细聊Spring Cloud Bus

Spring 事件驱动模型

因为Spring Cloud Bus的运行机制也是Spring事件驱动模型所以需要先了解相关知识点:



上面图中是Spring事件驱动模型的实现示意图,以下再补充一些图中未提现的实现细节:抽象类abstract class AbstractApplicationEventMulticaster中根据事件和事件类型获取对应的观察者的方法是:

	protected Collection<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType)

该方法内具体检索监听器(观察者的方法)是:

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) .....
// Add programmatically registered listeners, including ones coming
// from ApplicationListenerDetector (singleton beans and inner beans).
for (ApplicationListener<?> listener : listeners) {
if (supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
retriever.applicationListeners.add(listener);
}
allListeners.add(listener);
}
}
.....

此方法内根据传入参数事的件对象遍历所有对应(订阅)的监听者,其中有个很重要的方法boolean supportsEvent,此方法用于判断是否是订阅的监听者:

	protected boolean supportsEvent(
ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) { GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

其中接口GenericApplicationListener和GenericApplicationListenerAdapter类都是为了定义或实现supportsEventType方法和supportsSourceType方法,通过这两个方法确定是否是事件的监听器(观察者、订阅者)。

public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

	/**
* Determine whether this listener actually supports the given event type.
* @param eventType the event type (never {@code null})
*/
boolean supportsEventType(ResolvableType eventType); /**
* Determine whether this listener actually supports the given source type.
* <p>The default implementation always returns {@code true}.
* @param sourceType the source type, or {@code null} if no source
*/
default boolean supportsSourceType(@Nullable Class<?> sourceType) {
return true;
} /**
* Determine this listener's order in a set of listeners for the same event.
* <p>The default implementation returns {@link #LOWEST_PRECEDENCE}.
*/
@Override
default int getOrder() {
return LOWEST_PRECEDENCE;
} }

其中判断发布事件的来源对象supportsSourceType方法默认就返回true,意味着如果不重写这个接口方法,是否是订阅事件的监听器不以事件来源对象进行判断,只根据事件类型进行筛选,该方法的具体实现可参考GenericApplicationListenerAdapter类包装的supportsSourceType方法实现:

public boolean supportsSourceType(@Nullable Class<?> sourceType) {
return !(this.delegate instanceof SmartApplicationListener) ||
((SmartApplicationListener) this.delegate).supportsSourceType(sourceType);
}

Spring Cloud Bus的事件、发布、订阅

Spring Cloud Bus的事件都继承于RemoteApplicationEvent类,RemoteApplicationEvent类继承于Spring事件驱动模型的事件抽象类ApplicationEvent,也就说Spring Cloud Bus的事件、发布、订阅也是基于Spring的事件驱动模型,例如Spring Cloud Bus的配置刷新事件RefreshRemoteApplicationEvent:

同理订阅事件也是标准的Spring事件驱动模型,例如配置刷新的监听器源码继承了Spring事件驱动模型中的接口ApplicationListener<E extends ApplicationEvent>:

public class RefreshListener
implements ApplicationListener<RefreshRemoteApplicationEvent> { private static Log log = LogFactory.getLog(RefreshListener.class); private ContextRefresher contextRefresher; public RefreshListener(ContextRefresher contextRefresher) {
this.contextRefresher = contextRefresher;
} @Override
public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
Set<String> keys = this.contextRefresher.refresh();
log.info("Received remote refresh request. Keys refreshed " + keys);
} }

在BusRefreshAutoConfiguration类中会将RefreshListener对象注册到Spring的BeanFactory中(不把监听器类注册到Spring的BeanFactory中就无法利用Spring的事件驱动模型对刷新事件进行处理)。

	@Bean
@ConditionalOnProperty(value = "spring.cloud.bus.refresh.enabled",matchIfMissing = true)
@ConditionalOnBean(ContextRefresher.class)
public RefreshListener refreshListener(ContextRefresher contextRefresher) {
return new RefreshListener(contextRefresher);
}

也可以使用@EventListener创建监听器,例如TraceListener类:

	@EventListener
public void onAck(AckRemoteApplicationEvent event) {
Map<String, Object> trace = getReceivedTrace(event);
// FIXME boot 2 this.repository.add(trace);
} @EventListener
public void onSend(SentApplicationEvent event) {
Map<String, Object> trace = getSentTrace(event);
// FIXME boot 2 this.repository.add(trace);
}

发布事件也是利用应用程序上下文进行事件发布,比如配置刷新的实现代码:

@Endpoint(id = "bus-refresh") // TODO: document new id
public class RefreshBusEndpoint extends AbstractBusEndpoint { public RefreshBusEndpoint(ApplicationEventPublisher context, String id) {
super(context, id);
} @WriteOperation
public void busRefreshWithDestination(@Selector String destination) { // TODO:
// document
// destination
publish(new RefreshRemoteApplicationEvent(this, getInstanceId(), destination));
} @WriteOperation
public void busRefresh() {
publish(new RefreshRemoteApplicationEvent(this, getInstanceId(), null));
} }

注解@WriteOperation实现POST操作,@Endpoint结合management.endpoints.web.exposure.include=* 配置项可实现一个接入点,接入点的URL是:/actuator/bus-refresh

父类AbstractBusEndpoint内用应用程序上下文实现事件的发布:

public class AbstractBusEndpoint {

	private ApplicationEventPublisher context;

	private String appId;

	public AbstractBusEndpoint(ApplicationEventPublisher context, String appId) {
this.context = context;
this.appId = appId;
} protected String getInstanceId() {
return this.appId;
} protected void publish(ApplicationEvent event) {
this.context.publishEvent(event);
} }

Spring Cloud Bus的底层通讯实现(对使用者透明)

Spring Cloud Bus的底层通讯基础是Spring Cloud Stream,定义发送总线事件和接收总线事件监听器的类是BusAutoConfiguration(在网络上发送和接收其他节点的事件消息),因为继承了ApplicationEventPublisherAware所以该类也具备发布本地事件的功能(可以查询Aware接口作用),发布网络事件消息的方法是:

@EventListener(classes = RemoteApplicationEvent.class)
public void acceptLocal(RemoteApplicationEvent event) {
if (this.serviceMatcher.isFromSelf(event)
&& !(event instanceof AckRemoteApplicationEvent)) {
this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(event).build());
}
}

如果监听到RemoteApplicationEvent类事件,首先检查是否是自己发布并且不是ACK事件,如果是自己发布的非ACK事件就在总线上发送这个事件消息。发送AckRemoteApplicationEvent(ACK事件)已经在接收其他节点发的事件消息时触发了,所以这里不用管发送ACK事件的工作了。

接收事件消息:

@StreamListener(SpringCloudBusClient.INPUT)
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);
}
// If it's an ACK we are finished processing at this point
return;
}
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) {
// We are set to register sent events so publish it for local consumption,
// irrespective of the origin
this.applicationEventPublisher.publishEvent(new SentApplicationEvent(this,
event.getOriginService(), event.getDestinationService(),
event.getId(), event.getClass()));
}
}

接收到其他节点发来的事件消息后会将此事件发布到本地的应用程序上下文中(this.applicationEventPublisher),监听此事件类型的订阅者就会相应的进行处理。

两个跟踪事件AckRemoteApplicationEvent和SentApplicationEvent

从他们的继承关系可以看出,AckRemoteApplicationEvent可以发送到其他网络节点(继承于RemoteApplicationEvent),SentApplicationEvent只是本地事件(继承于ApplicationEvent),SentApplicationEvent事件可以显示收到事件消息的类型,AckRemoteApplicationEvent事件只显示收到事件消息的ID,TraceListener类负责监听和记录他们的内容(配置项要打开spring.cloud.bus.trace.enabled=true):

public class TraceListener {

	@EventListener
public void onAck(AckRemoteApplicationEvent event) {
Map<String, Object> trace = getReceivedTrace(event);
// FIXME boot 2 this.repository.add(trace);
} @EventListener
public void onSend(SentApplicationEvent event) {
Map<String, Object> trace = getSentTrace(event);
// FIXME boot 2 this.repository.add(trace);
} protected Map<String, Object> getSentTrace(SentApplicationEvent event) {
.....
} protected Map<String, Object> getReceivedTrace(AckRemoteApplicationEvent event) {
.....
} }

在总线事件发送端和总线事件接收端日志的记录流程如下:

测试A应用和B应用进行“聊天”

首先准备环境:

创建3个项目:spring-cloud-bus-shared-library、spring-cloud-bus-a、spring-cloud-bus-b

  • spring-cloud-bus-shared-library:负责定义事件和监听器还有配置类
  • spring-cloud-bus-a:扮演A应用负责引用shared-library并利用BUS发送消息给B应用(此消息实际为广播消息)
  • spring-cloud-bus-b:扮演B应用负责引用shared-library并利用BUS回复A应用发来的消息(此消息非广播消息)

spring-cloud-bus-shared-library的POM的依赖项:

<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties> <dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>

删除构建的Maven插件节点否则构建后其他项目引用不了(格式不对):

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

启动一个rabbitmq:

docker pull rabbitmq:3-management

docker run -d --hostname my-rabbit --name rabbit -p 5672:5672 -p 15672:15672 rabbitmq:3-management

application.properties配置定义:

spring.application.name=spring-cloud-bus-shared-library
server.port=9007
# 开启消息跟踪
spring.cloud.bus.trace.enabled=true
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest #显示的暴露接入点
management.endpoints.web.exposure.include=*

spring-cloud-bus-a、spring-cloud-bus-b的配置信息除了spring.application.name和server.port不一样,其他都是一样的。

自定义一个聊天事件类:

/**
* 聊天事件
*/
public class ChatRemoteApplicationEvent extends RemoteApplicationEvent { private String message; //for serializers
private ChatRemoteApplicationEvent(){} public ChatRemoteApplicationEvent(Object source, String originService,
String destinationService,String message){
super(source, originService, destinationService); this.message = message;
} public void setMessage(String message){
this.message = message;
} public String getMessage(){
return this.message;
}
}

自定义聊天事件监听器:

/**
* 聊天事件监听
*/
public class ChatListener implements ApplicationListener<ChatRemoteApplicationEvent> { private static Log log = LogFactory.getLog(ChatListener.class); public ChatListener(){} @Override
public void onApplicationEvent(ChatRemoteApplicationEvent event){
log.info(String.format("应用%s对应用%s悄悄的说:\"%s\"",
event.getOriginService(),
event.getDestinationService(),
event.getMessage()));
}
}

配置类将监听器注册到BeanFactory中,并需要显示的告诉Spring Cloud Bus我们有一个自定义事件:@RemoteApplicationEventScan(basePackageClasses=ChatRemoteApplicationEvent.class),否则BUS收到消息后无法识别事件类型。

@Configuration
@ConditionalOnClass(ChatListener.class)
@RemoteApplicationEventScan(basePackageClasses=ChatRemoteApplicationEvent.class)
public class BusChatConfiguration { @Bean
public ChatListener ChatListener(){
return new ChatListener();
}
}

发布到本地Maven仓库:

mvn install

spring-cloud-bus-a、spring-cloud-bus-b的POM依赖:

<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.bluersw</groupId>
<artifactId>spring-cloud-bus-shared-library</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies> <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

在spring-cloud-bus-a、spring-cloud-bus-b的启动Main函数上增加@ComponentScan(value = "com.bluersw")注解,否则不会扫描引用spring-cloud-bus-shared-library项目的配置类(也就加载不了自定义的事件和监听器类型)。

spring-cloud-bus-a:

@SpringBootApplication
@ComponentScan(value = "com.bluersw")
public class SpringCloudBusAApplication { public static void main(String[] args) {
SpringApplication.run(SpringCloudBusAApplication.class, args);
} }

spring-cloud-bus-b:

@SpringBootApplication
@ComponentScan(value = "com.bluersw")
public class SpringCloudBusBApplication { public static void main(String[] args) {
SpringApplication.run(SpringCloudBusBApplication.class, args);
} }

spring-cloud-bus-a发送消息给spring-cloud-bus-b(启动spring-cloud-bus-a程序和spring-cloud-bus-b程序):

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringCloudBusAApplicationTests { @Autowired
private ApplicationEventPublisher context; @Autowired
private BusProperties bp; @Test
public void AChat() {
context.publishEvent(new ChatRemoteApplicationEvent(this,bp.getId(),null,"hi!B应用,我是A应用,。"));
} }

执行AChat()之后,spring-cloud-bus-b的控制台会输出:

”应用spring-cloud-bus-a

细聊Spring Cloud Bus的更多相关文章

  1. 跟我学SpringCloud | 第八篇:Spring Cloud Bus 消息总线

    SpringCloud系列教程 | 第八篇:Spring Cloud Bus 消息总线 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如无特 ...

  2. Spring Cloud Bus介绍--Spring Cloud学习第七天(非原创)

    一.什么是Spring Cloud Bus二.Spring Cloud Bus之RabbitMQ介绍三.Spring Cloud Bus整合RabbitMQ四.Spring Cloud Bus整合Ka ...

  3. SpringCloud教程 | 第八篇: 消息总线(Spring Cloud Bus)

    一.安装rabbitmq 二.pom父文件 <?xml version="1.0" encoding="UTF-8"?> <project x ...

  4. 第七篇: 消息总线(Spring Cloud Bus)

    Spring Cloud Bus 将分布式的节点用轻量的消息代理连接起来.它可以用于广播配置文件的更改或者服务之间的通讯,也可以用于监控.本文要讲述的是用Spring Cloud Bus实现通知微服务 ...

  5. spring cloud 使用spring cloud bus自动刷新配置

    Spring Cloud Bus提供了批量刷新配置的机制,它使用轻量级的消息代理(例如RabbitMQ.Kafka等)连接分布式系统的节点,这样就可以通过Spring Cloud Bus广播配置的变化 ...

  6. Spring Boot + Spring Cloud 构建微服务系统(十):配置中心(Spring Cloud Bus)

    技术背景 我们在上一篇讲到,Spring Boot程序只在启动的时候加载配置文件信息,这样在GIT仓库配置修改之后,虽然配置中心服务器能够读取最新的提交信息,但是配置中心客户端却不会重新读取,以至于不 ...

  7. spring cloud bus原理总结

    1.spring cloud bus spring cloud是按照spring的配置对一系列微服务框架的集成,spring cloud bus是其中一个微服务框架,用于实现微服务之间的通信. spr ...

  8. Spring Cloud Bus 消息总线 RabbitMQ

    Spring Cloud Bus将分布式系统中各节点通过轻量级消息代理连接起来. 从而实现例如广播状态改变(例如配置改变)或其他的管理指令. 目前唯一的实现是使用AMQP代理作为传输对象. Sprin ...

  9. 第九章 消息总线: Spring Cloud Bus

    在微服务架构的系统中, 我们通常会使用轻量级的消息代理来构建一个共用的消息主题让系统中所有微服务实例都连接上来, 由于该主题中产生的消息会被所有实例监听和消费, 所以我们称它为消息总线. 在总线上的各 ...

随机推荐

  1. 部分DOM事件总结

    复习: 1.1 DOM:Docment Object Model  文档对象模型 当页面加载时,就会创建文档对象模型.文档对象模型被构造为DOM树: DOM树种任何一个部分都可以看做是节点对象,结构中 ...

  2. JAVA对象序列化和反序列化学习

    JAVA序列化就是将JAVA对象转化为字节序列的过程,而JAVA反序列化就是将字节序列转化为JAVA对象的过程. 这一过程是通过JAVA虚拟机独立完成,所以一个对象序列化后可以在任意时间和任意机器上反 ...

  3. Java编码技巧与代码优化

    本文参考整理自https://mp.weixin.qq.com/s/-u6ytFRp-ZAqdLBsMmuDMw 对于在本文中有所疑问的点可以去该文章查看详情 常量&变量 直接赋值常量值, 禁 ...

  4. 2018-8-10-xaml-添加-region

    title author date CreateTime categories xaml 添加 region lindexi 2018-08-10 19:16:51 +0800 2018-03-15 ...

  5. rsync 和 inotify 结合

    我们知道 rsync 可以实现推送和拉取,而 inotify-tools 借助内核的 inotify 机制实现了文件的 实时监控.因此,借助这个思路,我们可以通过使用 shell 脚本,调整 inot ...

  6. 四轴飞行器飞行原理与双闭环PID控制

    四轴轴飞行器是微型飞行器的其中一种,相对于固定翼飞行器,它的方向控制灵活.抗干扰能力强.飞行稳定,能够携带一定的负载和有悬停功能,因此能够很好地进行空中拍摄.监视.侦查等功能,在军事和民用上具备广泛的 ...

  7. leetcode69. x 的平方根 🌟

    题目: 实现 int sqrt(int x) 函数. 计算并返回 x 的平方根,其中 x 是非负整数. 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去. 示例 1: 输入: 4 输出: 2 ...

  8. poj 2689 Prime Distance(区间筛选素数)

    Prime Distance Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 9944   Accepted: 2677 De ...

  9. [IM002] [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序

    Problems meet in the project: [IM002] [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序((IM002) [Microso ...

  10. Proto3语法翻译

    本文主要对proto3语法翻译.参考网址:https://developers.google.com/protocol-buffers/docs/proto3 defining a message t ...