Spring Cloud Bus使用轻量级消息代理将分布式系统的节点连接起来。然后可以使用此广播状态更改(例如配置更改)或其他管理指令。 Spring Cloud 体系中的一个组件 Spring Cloud Bus ,要分析Spring Cloud Bus,建议先熟悉 Spring Cloud Stream,不然无法理解 Spring Cloud Bus 内部的代码。
要使用Bus,首先需要在pom文件中引用。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
在spring-cloud-starter-bus-amqp中,可以看到又依赖了Spring Stream组件spring-cloud-starter-stream-rabbit和spring-cloud-bus。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
</dependencies>
Stream已经分析过了,直接看spring-cloud-bus组成。看起来内容好少。
在spring.factories文件中
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.bus.BusAutoConfiguration,\
org.springframework.cloud.bus.jackson.BusJacksonAutoConfiguration
# Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.bus.BusEnvironmentPostProcessor
直接看BusAutoConfiguration。
@Configuration
@ConditionalOnBusEnabled
@EnableBinding({SpringCloudBusClient.class})
@EnableConfigurationProperties({BusProperties.class})
public class BusAutoConfiguration implements ApplicationEventPublisherAware {
public static final String BUS_PATH_MATCHER_NAME = "busPathMatcher";
@Autowired
@Output("springCloudBusOutput")
private MessageChannel cloudBusOutboundChannel;
@Autowired
private ServiceMatcher serviceMatcher;
@Autowired
..................
}
又看到了@EnableBinding注解,这次注解的value为SpringCloudBusClient,作用类似我们之前在Stream中自己的StreamSendClient测试实例。
public interface SpringCloudBusClient {
String INPUT = "springCloudBusInput";
String OUTPUT = "springCloudBusOutput";
@Output("springCloudBusOutput")
MessageChannel springCloudBusOutput();
@Input("springCloudBusInput")
SubscribableChannel springCloudBusInput();
}
按照之前Steam的源码分析,@EnableBinding(SpringCloudBusClient)这里会创建:
1、BeanName为springCloudBusOutput,生成该Bean的工厂类为SpringCloudBusClient,生成Bean的方法为springCloudBusOutput。
2、BeanName为springCloudBusInput,生成该Bean的工厂类为SpringCloudBusClient,生成Bean的方法为springCloudBusInput。
3、BeanName为SpringCloudBusClient,beanclass为BindableProxyFactory。BindableProxyFactory通过动态代理根据方法返回不同的MessageChannel。
4、并根据BindingServiceConfiguration的Bean对象StreamListenerAnnotationBeanPostProcessor处理输入输出的关联关系。
继续看BusAutoConfiguration其他方法。
@EventListener(
classes = {RemoteApplicationEvent.class}
)
public void acceptLocal(RemoteApplicationEvent event) {
if (this.serviceMatcher.isFromSelf(event) && !(event instanceof AckRemoteApplicationEvent)) {
this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(event).build());
}
}
@StreamListener("springCloudBusInput")
public void acceptRemote(RemoteApplicationEvent event) {
if (event instanceof AckRemoteApplicationEvent) {
if (this.bus.getTrace().isEnabled() && !this.serviceMatcher.isFromSelf(event) && this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent(event);
}
} else {
if (this.serviceMatcher.isForSelf(event) && this.applicationEventPublisher != null) {
if (!this.serviceMatcher.isFromSelf(event)) {
this.applicationEventPublisher.publishEvent(event);
}
if (this.bus.getAck().isEnabled()) {
AckRemoteApplicationEvent ack = new AckRemoteApplicationEvent(this, this.serviceMatcher.getServiceId(), this.bus.getAck().getDestinationService(), event.getDestinationService(), event.getId(), event.getClass());
this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(ack).build());
this.applicationEventPublisher.publishEvent(ack);
}
}
if (this.bus.getTrace().isEnabled() && this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent(new SentApplicationEvent(this, event.getOriginService(), event.getDestinationService(), event.getId(), event.getClass()));
}
}
}
@Configuration
@ConditionalOnClass({Endpoint.class, RefreshScope.class})
@ConditionalOnBean({ContextRefresher.class})
protected static class BusRefreshConfiguration {
protected BusRefreshConfiguration() {
}
@Bean
@ConditionalOnProperty(
value = {"spring.cloud.bus.refresh.enabled"},
matchIfMissing = true
)
public RefreshListener refreshListener(ContextRefresher contextRefresher) {
return new RefreshListener(contextRefresher);
}
@Configuration
@ConditionalOnProperty(
value = {"endpoints.spring.cloud.bus.refresh.enabled"},
matchIfMissing = true
)
protected static class BusRefreshEndpointConfiguration {
protected BusRefreshEndpointConfiguration() {
}
@Bean
public RefreshBusEndpoint refreshBusEndpoint(ApplicationContext context, BusEndpoint busEndpoint) {
return new RefreshBusEndpoint(context, context.getId(), busEndpoint);
}
}
}
@ManagedResource
public class RefreshBusEndpoint extends AbstractBusEndpoint {
public RefreshBusEndpoint(ApplicationEventPublisher context, String id, BusEndpoint delegate) {
super(context, id, delegate);
}
@RequestMapping(
value = {"refresh"},
method = {RequestMethod.POST}
)
@ResponseBody
@ManagedOperation
public void refresh(@RequestParam(value = "destination",required = false) String destination) {
this.publish(new RefreshRemoteApplicationEvent(this, this.getInstanceId(), destination));
}
}
@Bean
@ConditionalOnProperty(
value = {"spring.cloud.bus.refresh.enabled"},
matchIfMissing = true
)
public RefreshListener refreshListener(ContextRefresher contextRefresher) {
return new RefreshListener(contextRefresher);
}
public class RefreshListener implements ApplicationListener<RefreshRemoteApplicationEvent> {
private static Log log = LogFactory.getLog(RefreshListener.class);
private ContextRefresher contextRefresher;
public RefreshListener(ContextRefresher contextRefresher) {
this.contextRefresher = contextRefresher;
}
public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
Set<String> keys = this.contextRefresher.refresh();
log.info("Received remote refresh request. Keys refreshed " + keys);
}
}
acceptLocal方法上有@EventListener注解,会将该方法注入到spring监听中。
同时我们可以看到acceptRemote方法上也有@StreamListener("springCloudBusInput")注解,说明这里也建立了对rabbitmq信息的监听。
下面介绍一下调用的过程,当我们修改完Spring Config Server服务器的配置文件后,调用 http://localhost:xxxx/actuator/bus/refresh 时,会经历以下步骤:
1、/refresh请求会进入RefreshBusEndpoint.refresh方法,之后会进行推送
2、进入标有@EventListener的acceptLocal方法,并判断事件是否源自自己,如果是,则通过cloudBusOutboundChannel发送到RabbitMq中。同时还会触发RefreshListener.onApplicationEvent方法进行获取并刷新配置文件。
3、当消息发送到Mq后,会调用所有服务实现了acceptRemote方法(包括eurekaclient1、2、3、...)等等。之后会走这段逻辑
if (this.serviceMatcher.isForSelf(event) && this.applicationEventPublisher != null) {
if (!this.serviceMatcher.isFromSelf(event)) {
this.applicationEventPublisher.publishEvent(event);
}
}
先判断事件是否给自己,再判断事件是否来源于自己。对于eurekaclient来说,事件源于spring config server,所以事件是通知给自己,但是不来源于自己,便会知道需要去从configserver获取新的配置信息。调用applicationEventPublisher.publishEvent(event)方法,触发RefreshListener.onApplicationEvent方法进行获取并刷新配置文件。
以上便是Spring Cloud Bus的大体思路,Bus还是很简单,前提一定是要动Spring Cloud Stream才会更好的理解。