Spring Cloud Bus 消息总线与动态刷新
Spring Cloud Bus 通过轻量消息代理(RabbitMQ / Kafka)把节点连接成一条"总线",让配置变更等事件一次触发、全集群响应。配合 @RefreshScope 与 Config 中心,实现配置变更的自动刷新。
为什么需要 Bus
没有 Bus 时,Config Server 配置变更后:
❌ 每个客户端手动 POST /actuator/refresh → 服务多时不可接受
❌ 逐个调用 → 顺序、失败重试都要自己处理Bus 的做法:
✅ Config Server / 任意客户端发布刷新事件 → 消息代理广播 → 所有订阅的客户端自动刷新┌──────────┐ ┌──────────┐ ┌──────────┐
│ 服务 A │ │ 服务 B │ │ 服务 C │
│ (订阅) │ │ (订阅) │ │ (订阅) │
└────┬─────┘ └────┬─────┘ └────┬─────┘
└──────┬─────────┴──────────┬─────┘
│ RabbitMQ │
└──────────┬──────────┘
│ 发布 refresh 事件
┌─────┴─────┐
│ 任一节点/Server │
└───────────┘@RefreshScope 原理
@RefreshScope 是动态刷新的基础,它是一个自定义 Scope:
java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh") // 关键:Bean 使用名为 refresh 的 Scope
public @interface RefreshScope { }RefreshScope 的实现
java
// org.springframework.cloud.context.scope.refresh.RefreshScope
public class RefreshScope extends GenericScope implements Scope, DisposableBean {
private boolean eager = false;
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// GenericScope 缓存中取 Bean,取不到则创建并缓存
return super.get(name, objectFactory);
}
// 核心方法:刷新时销毁所有 refresh Scope 的 Bean
public boolean refreshAll() {
super.destroy(); // 销毁所有缓存实例
return true;
}
}刷新流程
POST /actuator/refresh
│
▼
RefreshEndpoint → RefreshEventListener
│
├─ 1. 重新拉取配置(ConfigServicePropertySourceLocator)
│ └─ Environment 中更新 PropertySource
├─ 2. context.refresh() 发布 ContextRefreshedEvent
│ └─ RefreshScope.refreshAll() 销毁所有 @RefreshScope Bean
└─ 3. 下次使用时重新创建 Bean,@Value 重新注入新值使用示例
java
@RestController
@RefreshScope // 每次刷新后重建
public class OrderController {
@Value("${order.discount}")
private String discount; // 刷新后拿到新值
}注意:只有 @RefreshScope 标记的 Bean 会被重建;普通 Bean 的 @Value 刷新后不更新。
Bus 事件模型
远程事件(RemoteApplicationEvent)
Bus 传播的事件是 RemoteApplicationEvent 的子类:
java
// org.springframework.cloud.bus.event.RemoteApplicationEvent
public abstract class RemoteApplicationEvent extends ApplicationEvent {
private final String originService; // 事件来源服务
private final String destinationService; // 目标服务(* 为全部)
}内置事件:
| 事件 | 用途 |
|---|---|
| RefreshRemoteApplicationEvent | 触发目标服务配置刷新 |
| EnvironmentChangeRemoteApplicationEvent | 环境变更通知 |
| HeartbeatEvent | 心跳(Bus 实例状态) |
| AckRemoteApplicationEvent | 事件确认回执 |
事件传播链路
发布方(任意服务/Config Server)
│
├─ BusBridge(org.springframework.cloud.bus.BusBridge)
│ └─ busBridge.send(event) → 发布到消息代理
▼
消息代理(RabbitMQ/Kafka)→ 所有订阅该队列的客户端
│
▼
接收方
├─ BusAutoConfiguration 注册的监听器
├─ 检查 destinationService:匹配自己或 * 才处理
└─ 调用本地 Spring 事件发布 → @EventListener 处理关键组件
| 组件 | 作用 |
|---|---|
| BusBridge | 发送远程事件的入口接口 |
| BusProperties | 总线配置(id、destination、acks 等) |
| ServiceMatcher | 判断事件是否属于当前服务 |
| PathDestinationFactory | 目标服务名解析(** 通配) |
配置变更自动刷新全流程
1. 依赖与配置
xml
<!-- Config Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>xml
<!-- Config Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>yaml
# 客户端配置
management:
endpoints:
web:
exposure:
include: busrefresh, refresh # 暴露刷新端点
spring:
rabbitmq:
host: mq-server
cloud:
bus:
id: ${spring.application.name}:${spring.cloud.client.hostname}:${random.uuid}2. 完整刷新链路
1. 运维修改 Git 配置仓库并提交
│
2. 触发刷新(两种方式)
├─ 方式 A:POST Config Server /actuator/busrefresh
│ Server 作为发布方广播 RefreshRemoteApplicationEvent
└─ 方式 B:POST 任一客户端 /actuator/busrefresh
该客户端广播事件
│
3. 消息代理把事件分发给所有订阅客户端
│
4. 各客户端收到 RefreshRemoteApplicationEvent
│
5. 客户端拉取 Config Server 最新配置(Environment 更新)
│
6. RefreshScope.refreshAll() 销毁并重建 @RefreshScope Bean
│
7. 配置生效定向刷新
Bus 支持只刷新部分服务:
bash
# 刷新全部服务
POST /actuator/busrefresh
# 只刷新 order-service 的所有实例
POST /actuator/busrefresh/order-service
# 只刷新 order-service 的指定实例
POST /actuator/busrefresh/order-service:192.168.1.10:8080目标匹配由 ServiceMatcher + 事件 destinationService 实现。
与 Nacos 配置中心的对比
| 对比 | Config + Bus | Nacos Config |
|---|---|---|
| 配置存储 | Git 仓库 | Nacos 存储 |
| 变更感知 | Bus 广播 refresh 事件 | 长轮询 + MD5 比对 |
| 自动刷新 | @RefreshScope 重建 | Listener 回调 + @RefreshScope |
| 组件数 | Config Server + Bus + MQ | 一个 Nacos |
| 运维复杂度 | 较高 | 低 |
Nacos 方案更简洁(长轮询天然感知变更),因此国内 Alibaba 体系普遍用 Nacos 替代 Config + Bus。
常见问题
- Bus 事件没收到? 检查所有节点是否连到同一 MQ、
spring.cloud.bus.id是否唯一、destination 是否匹配。 - 刷新了但配置没变?
@RefreshScope只重建标记 Bean;确认目标 Bean 已标注,且值来自远端 PropertySource。 - 广播风暴? 事件会传给所有节点,包括发布方自己(ServiceMatcher 会过滤);多实例广播注意消息幂等。
- Bus 可以自定义事件吗? 可以,继承
RemoteApplicationEvent,用@RemoteApplicationEventScan扫描自定义事件类。