1.什么是消息总线
由于配置信息的变更或者其他的一些管理操作,所以需要消息总线。消息总线的意思是使用轻量级的消息代理来构建一个共用的的消息主题让系统中所有的微服务实例都连接上来,该主题中产生的消息会被所有实例监听和消费。
2.消息代理
消息代理是一种消息验证、传输、路由的架构模式。它是一个中间件产品,它的核心是一个消息的路由程序,用来实现接受和分发消息,并根据设定好的消息处理流来转发给正确的应用。
使用场景
- 将消息路由到一个或多个目的
- 消息转化为其他的表现方式
- 执行消息的聚集、消息的分解,并将结果发送到他们的目的地,然后重新组合响应返回给消息用户。
- 调用Web服务来检索数据
- 响应事件或错误
- 使用发布-订阅模式来提供内容或基于主体的消息路由
3.RabbitMQ实现消息总线
rabbitmq是实现了AMQP协议的开源消息代理软件,也称为面向消息的中间件。
基本概念
- Broker:可以理解为消息队列服务器的实体,它是一个中间间应用,负责接收消息生产者的消息,然后将消息发送至消息接受者或者其他的Borker.
- Exchange:消息交换机,是消息第一个到达的地方,消息通过它指定的路由规则,分发到不同的消息队列中去。
- Queue:消息队列,消息通过发送和路由之后最终到达的地方,到达Queue的消息即进入逻辑上等待消费的状态。每个消息通过发送到一个或多个队列
- Binding:绑定,它的作用就是把Exchange和Queue按照路由规则绑定起来,也就是Exchange和Queue之间的虚拟连接
- Routing key:路由关键字,Exchange根据这个关键字进行消息投递
- Virtual host:虚拟主机,它是对Broker的虚拟划分,将消费者、生产者和他们依赖的AMQP相关结构进行隔离,一般都是为了安全考虑。比如,我们可以在一个Broker中设置多个虚拟主机,对不同用户进行权限的分离
- Connection:连接,代表生产者、消费者、broker之间进行通信的物理网络
- Channel:消息通道,用于连接生产者、消费者的逻辑结构。在客户端的每个连接里,可建立多个Channel,每个Channel代表一个会话任务,通过Channel可以隔离同一连接中的不同交互内容。
- Producer:消息生产者,制造消息并发送消息的程序
- Consumer:消息消费者,接受消息并处理消息的程序
消息投递到队列的整个过程
客户端连接到消息队列服务器,打开一个Channel
客户端声明一个Exchange,并设置相关属性
客户端声明一个Queue,并设置相关属性
客户端使用Routing Key,在Exchange和Queue之间建立好绑定关系
客户端投递消息到Exchange
Exchange接受到消息后,根据消息的Key和已经设置的Binding,进行消息路由,将消息投递到一个或多个Queue里。
Exchange类型
- Direct交换机:完全根据Key进行投递。比如,绑定时设置了Routing Key为abc,那么客户端提交的信息,只有设置了Key为abc的才会被投递到队列。
- Topic交换机:对Key进行模式匹配后进行投递,可以使用符号#匹配一个或多个词,符号*匹配正好一个词。比如,abc.#匹配abc.def.gfi ; abc.* 只匹配abc.def
- Fanout交换机:不需要任何Key,它采取广播模式,一个消息进来时,投递到与该交换机绑定的所有队列。
消息队列持久化
将数据写入磁盘(durable持久化)
- Exchange持久化,在声明时指定durable=>1
- Queue持久化,在声明时指定durable=>1
- 消息持久化,在投递时指定delivery_mode=>2 (1是非持久化)
如果Exchange和Queue是持久化的,那么Binding也是持久化的。如果有其中一个是非持久化,那么不允许绑定。
快速入门
1.首先确保开启了RabbitMQ服务端
2.新建一个服务实例,配置依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3.配置文件
包括rabbitmq的地址以及账号密码
spring.application.name=rabbitmq-hello
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
4.创建消息生产者
主要是注入一个AmqpTemplate对象进行操作
@Component
public class Sender {
@Autowired
private AmqpTemplate amqpTemplate;
public void send(){
String context="hello - llg- "+new Date();
System.out.println(context);
amqpTemplate.convertAndSend("hello",context);
}
}
5.创建消息消费者
主要是注入监听的消息队列名
@Component
@RabbitListener(queues = "hello")
public class Receiver {
@RabbitHandler
public void process(String hello){
System.out.println("Receiver:"+hello);
}
}
6.在配置文件上配置队列
@Configuration
public class RabbitConfig {
@Bean
public Queue helloQueue(){
return new Queue("hello");
}
}
总结:生产和消费是一个异步操作,这也是在分布式系统中要使用消息代理的原因,以此我们可以使用通信来解耦业务逻辑。
当消息发送之后,如果消费者没有消费,那么消息将会在Queue等候,直到消费者上线消费。
整合Spring Cloud Bus
利用消息代理将消息路由到一个或多个目的地实现动态刷新配置
大致过程:通过Eureka进行服务注册后,加入bus配置文件,通过端点/bus/refresh可以不仅刷新本实例的配置信息,还可以通过消息总线通知此服务的所有其他实例。这比端点/refresh的功能是不再仅仅刷新一个实例。
首先添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
然后添加配置信息
spring.cloud.config.fail-fast=true
spring.application.name=llg
server.port=7003
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
spring.cloud.config.profile=dev
spring.cloud.config.label=master
spring.cloud.config.uri=http://localhost:7001/
#spring.cloud.config.username=llg
#spring.cloud.config.password=1997729
management.security.enabled=false
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
最后通过访问/bus/refresh 通知所有服务实例
指定刷新范围
通过一个destination参数指定刷新范围
- /bus/refresh?destination=customers:9000 指定某一个实例
- /bus/refresh?destination=customers:** 指定某一个服务
架构优化(重点)
经过上述配置发现,如果对某一个实例进行Web Hook的话虽然可以对整个服务的所有实例进行刷新,但是这影响了每个实例的对等性。所以最后的法子是将这个请求/bus/refresh发送到config-server上面,这样子就不会影响了实例的对等性。在这里由于不再是请求发送到某个服务,所以需要使用destination指定某个服务。
RabbitMQ配置
配置详解
- spring.rabbitmq.address MQ地址,有多个的时候使用逗号分隔开来,由IP和Port组成
- spring.rabbitmq.cache.channel.checkout-timeout 当缓存已满时,获取Channel的等待时间,单位为毫秒
- spring.rabbitmq.cache.channel.size 缓存中保持的Channel数量
- spring.rabbitmq.cache.connection.mode=CHANNEL 连接缓存的模式
- spring.rabbitmq.cache.connection.size 缓存的连接数
- spring.rabbitmq.connection-timeout 连接超时参数,单位为毫秒;设置为“0”代表无穷大
- spring.rabbitmq.dynamic=true 默认创建一个AmqpAdmin的Bean
- spring.rabbitmq.host=localohost Rabbitmq的主机地址
- spring.rabbitmq.listener.acknowledge-mode 容器的acknowledge模式
- spring.rabbitmq.listener.auto-startup=true 启动时自动启动容器
- spring.rabbitmq.listener.concurrency 消费者的最小数量
- spring.rabbitmq.listener.default-requeue-rejected=true 投递失败时是否重新排队
- spring.rabbitmq.listener.max-concurrency 消费者的最大数量
- spring.rabbitmq.listener.prefetch 在单个请求处理中处理的消息个数,它应该大于等于事务数量
- spring.rabbitmq.listener.retry.enabled=false 不论是不是重试的发布
- spring.rabbitmq.listener.retry.initial-interval=1000 第一次与第二次投递尝试的时间间隔
- spring.rabbitmq.listener.retry.max-attempts=3 尝试投递消息的最大数量
- spring.rabbitmq.listener.retry.max-interval=10000 两次尝试的最大时间间隔
- spring.rabbitmq.listener.retry.multiplier=1.0 上一次尝试时间间隔的乘数
- spring.rabbitmq.listener.retry.stateless=true 不论重试是有状态的还是无状态的
- spring.rabbitmq.listener.transaction-size 在上一个事务中处理的消息数量。为了获得最佳效果,该值应设置为小于等于每个请求中处理的消息个数,即spring.rabbitmq.listener.listener.prefetch 的值
- spring.rabbitmq.password MQ密码
- spring.rabbitmq.port MQ端口
- spring.rabbitmq.publiisher-confirms=false 开启Publisher Confirm 机制
- spring.rabbitmq.publiisher-returns=false 开启Publisher return 机制
- spring.rabbitmq.requested-hearted 请求心跳超时时间,单位为秒
- spring.rabbitmq.ssl.enabled=false 启用SSL支持
- spring.rabbitmq.ssl.key-store 保存SSL证书地址
- spring.rabbitmq.ssl.key-store-password 访问SSL证书地址使用的密码
- spring.rabbitmq.ssl.trust-store SSL的可信地址
- spring.rabbitmq.ssl.trust-store-password 访问SSL的可信地址使用的密码
- spring.rabbitmq.ssl.algorithm SSL算法,默认使用Rabbit的客户端算法库
- spring.rabbitmq.template.mandatory=false 启用强制消息
- spring.rabbitmq.template.receive-timeout=0 receive()的超时时间
- spring.rabbitmq.template.reply-timeout=5000 sendAndReceive()的超时时间
- spring.rabbitmq.template.retry.enabled =false 设置为true的时候RabbitTemplate能够实现重试
- spring.rabbitmq.template.retry.initial-interval=1000 第一次与第二次发布消息的时间间隔
- spring.rabbitmq.template.retry.max-attempts=3 尝试发布消息的最大数量
- spring.rabbitmq.template.retry.multiplier 上一次尝试时间间隔的乘数 1.0
- spring.rabbitmq.username MQ账号
- spring.rabbitmq.virtual-host 连接到RabbitMQ的虚拟主机
4.Kafaka实现消息总线
略
5.深入理解-Spring事件
从/bus/refresh发送的请求来看,里面包含某些信息内容
RefreshRemoteApplicationEvent和AckRemoteApplicationEvent共有属性
- type:消息的时间类型,包含了RefreshRemoteApplicationEvent和AckRemoteApplicationEvent.其中,RefreshRemoteApplication是刷新配置的事件,而AckRemoteApplicationEvent是响应消息已经正确接收的告知消息事件
- timestamp:消息的时间戳
- originService:消息的来源服务实例
- destinationService:消息的目标服务实例 *:** 代表所有实例
- id:消息的唯一标识
AckRemoteApplicationEvent私有属性
ackId:对应的消息来源,例如RefreshRemoteApplicationEvent的id
ackDestinationService:Ack消息的目标服务实例
event:Ack消息的来源事件
事件驱动模型
由于spring-cloud-bus的底层是使用了spring的事件驱动,所以在这里先讲解事件驱动。
spring的时间驱动模型包括了三个基本概念:事件、事件监听者和事件发布者
作用:为Bean与Bean之间通信提供了可能,一个Bean执行完任务后可以通过发布事件使另一个Bean做出相应处理
事件
spring定义了事件的抽象类ApplicationEvent,包括了两个变量,一个是时间戳,而另一个比较特别就是source源事件对象
当我们需要自定义事件的时候只需要继承这个类就可以了,例如RefreshRemoteApplicationEvent和AckRemoteApplicationEvent
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp = System.currentTimeMillis();
public ApplicationEvent(Object source) {
super(source);
}
public final long getTimestamp() {
return this.timestamp;
}
}
事件监听者
spring定义了ApplicationListener接口,针对某个ApplicationEvent子类的监听和处理者
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
事件发布者
spring中定义了ApplicationEventPublisher和ApplicationEventMulticaster两个接口用来发布事件
ApplicationEventPublisher:定义了发布事件的函数
ApplicationEventMulticaster:定义了对ApplicationListener的维护操作(比如新增、移除等)以及将ApplicationEvent广播给可用ApplicationListener的操作
public interface ApplicationEventPublisher {
void publishEvent(ApplicationEvent var1);
void publishEvent(Object var1);
}
public interface ApplicationEventMulticaster {
void addApplicationListener(ApplicationListener<?> var1);
void addApplicationListenerBean(String var1);
void removeApplicationListener(ApplicationListener<?> var1);
void removeApplicationListenerBean(String var1);
void removeAllListeners();
void multicastEvent(ApplicationEvent var1);
void multicastEvent(ApplicationEvent var1, ResolvableType var2);
}
ApplicationEventPublish的实现类AbstractApplicationContext实现了publishEvent的函数,而这个函数里面可以发现最终会调用ApplicationEventMulticase的multicastEvent来具体实现发布事件给监听者的操作
protected void publishEvent(Object event, ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (this.logger.isTraceEnabled()) {
this.logger.trace("Publishing event in " + this.getDisplayName() + ": " + event);
}
Object applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent)event;
} else {
applicationEvent = new PayloadApplicationEvent(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
}
}
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
} else {
this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
}
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
} else {
this.parent.publishEvent(event);
}
}
}
所以接着我们去看ApplicationEventMulticaster的实现类SimpleApplicationEventMulticaster中,具体如下:
从代码中可以看到,通过遍历ApplicationListener集合,找到特定事件的监听器,然后调用监听器的onApplicationEvent的函数来做出对具体时间的处理(onApplicationEvent相当于onclick)
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
Iterator var4 = this.getApplicationListeners(event, type).iterator();
while(var4.hasNext()) {
final ApplicationListener<?> listener = (ApplicationListener)var4.next();
Executor executor = this.getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
public void run() {
SimpleApplicationEventMulticaster.this.invokeListener(listener, event);
}
});
} else {
this.invokeListener(listener, event);
}
}
}
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = this.getErrorHandler();
if (errorHandler != null) {
try {
this.doInvokeListener(listener, event);
} catch (Throwable var5) {
errorHandler.handleError(var5);
}
} else {
this.doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
} catch (ClassCastException var6) {
String msg = var6.getMessage();
if (msg != null && !this.matchesClassCastMessage(msg, event.getClass().getName())) {
throw var6;
}
Log logger = LogFactory.getLog(this.getClass());
if (logger.isDebugEnabled()) {
logger.debug("Non-matching event type for listener: " + listener, var6);
}
}
}
实战演练
首先定义事件
public class LLGEvent extends ApplicationEvent {
private String username;
private String password;
public LLGEvent(Object source, String username, String password) {
super(source);
this.username = username;
this.password = password;
}
}
接着配置监听器
@Component
public class LLGListener implements ApplicationListener<LLGEvent> {
@Override
public void onApplicationEvent(LLGEvent llgEvent) {
int i=23;
System.out.println(llgEvent.getPassword()+llgEvent.getPassword());
}
}
最后使用ApplicationContext发布事件,它实现了publishEvent方法
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConfigClientApplicationTests {
@Autowired
ApplicationContext applicationContext;
@Test
public void contextLoads() {
applicationContext.publishEvent(new LLGEvent(this,"llg","123456"));
}
}
6.深入理解spring-cloud-bus
事件定义
首先查看RemoteApplicationEvent
从头部注解可以看出jackson对多态类型的处理注解,当进行序列化时,会使用子类的名称作为type属性的值,比如之前示例中的“type”:"RefreshRemoteApplication"
@JsonTypeInfo(
use = Id.NAME,
property = "type"
)
@JsonIgnoreProperties({"source"})
public abstract class RemoteApplicationEvent extends ApplicationEvent {
private static final Object TRANSIENT_SOURCE = new Object();
private final String originService;
private final String destinationService;
private final String id;
接着看其他子类
RefreshRemoteApplicationEvent
从源代码可以看出,子类没有重写或者增加任何方法
public class RefreshRemoteApplicationEvent extends RemoteApplicationEvent {
private RefreshRemoteApplicationEvent() {
}
public RefreshRemoteApplicationEvent(Object source, String originService, String destinationService) {
super(source, originService, destinationService);
}
}
AckRemoteApplicationEvent
该事件用于告知某个事件消息已经被接受,通过该事件消息可以监控各个事件消息的响应
public class AckRemoteApplicationEvent extends RemoteApplicationEvent {
private final String ackId;
private final String ackDestinationService;
private Class<? extends RemoteApplicationEvent> event;
private AckRemoteApplicationEvent() {
this.ackDestinationService = null;
this.ackId = null;
this.event = null;
}
public AckRemoteApplicationEvent(Object source, String originService, String destinationService, String ackDestinationService, String ackId, Class<? extends RemoteApplicationEvent> type) {
super(source, originService, destinationService);
this.ackDestinationService = ackDestinationService;
this.ackId = ackId;
this.event = type;
}
public String getAckId() {
return this.ackId;
}
public String getAckDestinationService() {
return this.ackDestinationService;
}
public Class<? extends RemoteApplicationEvent> getEvent() {
return this.event;
}
@JsonProperty("event")
public void setEventName(String eventName) {
try {
this.event = Class.forName(eventName);
} catch (ClassNotFoundException var3) {
this.event = UnknownRemoteApplicationEvent.class;
}
}
EnvironmentChangeRemoteApplicationEvent
用于更新消息总线上每个节点的Spring环境属性。接受消息的节点就是根据该Map对象中的属性来覆盖本地的Spring环境属性
public class EnvironmentChangeRemoteApplicationEvent extends RemoteApplicationEvent {
private final Map<String, String> values;
private EnvironmentChangeRemoteApplicationEvent() {
this.values = null;
}
public EnvironmentChangeRemoteApplicationEvent(Object source, String originService, String destinationService, Map<String, String> values) {
super(source, originService, destinationService);
this.values = values;
}
public Map<String, String> getValues() {
return this.values;
}
SentApplicationEvent
主要用于发送信号来表示一个远程事件已经在系统中发送到某些地方了,它本身不是一个远程的事件,不会被发送到消息总线上
@JsonTypeInfo(
use = Id.NAME,
property = "type"
)
@JsonIgnoreProperties({"source"})
public class SentApplicationEvent extends ApplicationEvent {
private static final Object TRANSIENT_SOURCE = new Object();
private final String originService;
private final String destinationService;
private final String id;
private Class<? extends RemoteApplicationEvent> type;
protected SentApplicationEvent() {
this(TRANSIENT_SOURCE, (String)null, (String)null, (String)null, RemoteApplicationEvent.class);
}
public SentApplicationEvent(Object source, String originService, String destinationService, String id, Class<? extends RemoteApplicationEvent> type) {
super(source);
this.originService = originService;
this.type = type;
if (destinationService == null) {
destinationService = "*";
}
if (!destinationService.contains(":")) {
destinationService = destinationService + ":**";
}
this.destinationService = destinationService;
this.id = id;
}
事件监听器
RefreshListener和EnvironmentChangeListener都继承了Spring事件模型中的监听器接口ApplicationListener.
RefreshListener
从泛型中我们可以看到该监听器就是针对我们之前所介绍的RefreshRemoteApplicationEvent事件的,其中onApplicationEvent函数中调用了ContextRefresher 中 refresh()函数进行配置属性的刷新,从refresh可以看到还发布了一个EnvironmentChangeEvent事件.refresh俩面还有个change方法,里面有加载新的配置,通过加载远程git配置文件获得.并且方法是使用了RestTmeplate进行请求获取。
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);
}
}
public synchronized Set<String> refresh() {
Map<String, Object> before = this.extract(this.context.getEnvironment().getPropertySources());
this.addConfigFilesToEnvironment();
Set<String> keys = this.changes(before, this.extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
this.scope.refreshAll();
return keys;
}
所以我们接着去看EnvironmentChangeListener监听器
获取了之前提到的事件中定义的Map对象,然后通过遍历来更新EnvironmentManager中的属性内容
public class EnvironmentChangeListener implements ApplicationListener<EnvironmentChangeRemoteApplicationEvent> {
private static Log log = LogFactory.getLog(EnvironmentChangeListener.class);
@Autowired
private EnvironmentManager env;
public EnvironmentChangeListener() {
}
public void onApplicationEvent(EnvironmentChangeRemoteApplicationEvent event) {
Map<String, String> values = event.getValues();
log.info("Received remote environment change request. Keys/values to update " + values);
Iterator var3 = values.entrySet().iterator();
while(var3.hasNext()) {
Entry<String, String> entry = (Entry)var3.next();
this.env.setProperty((String)entry.getKey(), (String)entry.getValue());
}
}
}
事件跟踪
TraceListener
从代码中可以看到@EventLiisterner注解,自动注册为一个监听器bean.通过trace端点可以跟踪最近的100条数据,关于发送和接受到的Ack事件信息,信息存储在内存中。
spring.cloud.bus.trace.enabled=true 开启trace端点
public class TraceListener {
private static Log log = LogFactory.getLog(TraceListener.class);
private TraceRepository repository;
public TraceListener(TraceRepository repository) {
this.repository = repository;
}
@EventListener
public void onAck(AckRemoteApplicationEvent event) {
this.repository.add(this.getReceivedTrace(event));
}
@EventListener
public void onSend(SentApplicationEvent event) {
this.repository.add(this.getSentTrace(event));
}
protected Map<String, Object> getSentTrace(SentApplicationEvent event) {
Map<String, Object> map = new LinkedHashMap();
map.put("signal", "spring.cloud.bus.sent");
map.put("type", event.getType().getSimpleName());
map.put("id", event.getId());
map.put("origin", event.getOriginService());
map.put("destination", event.getDestinationService());
if (log.isDebugEnabled()) {
log.debug(map);
}
return map;
}
protected Map<String, Object> getReceivedTrace(AckRemoteApplicationEvent event) {
Map<String, Object> map = new LinkedHashMap();
map.put("signal", "spring.cloud.bus.ack");
map.put("event", event.getEvent().getSimpleName());
map.put("id", event.getAckId());
map.put("origin", event.getOriginService());
map.put("destination", event.getAckDestinationService());
if (log.isDebugEnabled()) {
log.debug(map);
}
return map;
}
}
事件发布
首先查看spring cloud bus 启动时加载的一些基础类和接口,包括自动化配置类BusAutoConfiguration 、属性定义类BusProperties等
该接口定义了发送消息的抽象接口
@Autowired
@Output("springCloudBusOutput")
private MessageChannel cloudBusOutboundChannel;
通过ServiceMatcher的函数方法,判断事件来源是否是自己,以及判断目标是否是自己,以此作为依据是否要响应信息来进行事件的处理
@Autowired
private ServiceMatcher serviceMatcher;
public boolean isFromSelf(RemoteApplicationEvent event) {
String originService = event.getOriginService();
String serviceId = this.getServiceId();
return this.matcher.match(originService, serviceId);
}
public boolean isForSelf(RemoteApplicationEvent event) {
String destinationService = event.getDestinationService();
return destinationService == null || destinationService.trim().isEmpty() || this.matcher.match(destinationService, this.getServiceId());
}
定义了消息服务的绑定属性
@Autowired
private BindingServiceProperties bindings;
定义了Spring Cloud Bus 的属性
@Autowired
private BusProperties bus;
spring.cloud.bus为配置文件前缀。destination和enabled属性分别定义了默认的队列(Queue)或主题(Topic)是否连接到消息总线,所以我们通过spring.cloud.bus.destination来修改消息总线使用的队列或主题名称,以及使用spring.cloud.bus.enabled属性来设置应用是否要连接到消息总线上
@ConfigurationProperties("spring.cloud.bus")
public class BusProperties {
private BusProperties.Env env = new BusProperties.Env();
private BusProperties.Refresh refresh = new BusProperties.Refresh();
private BusProperties.Ack ack = new BusProperties.Ack();
private BusProperties.Trace trace = new BusProperties.Trace();
private String destination = "springCloudBus";
private boolean enabled = true;
监听本地事件来进行消息的发送,可以看到有个send的方法,把事件发送了出去
@EventListener(
classes = {RemoteApplicationEvent.class}
)
public void acceptLocal(RemoteApplicationEvent event) {
if (this.serviceMatcher.isFromSelf(event) && !(event instanceof AckRemoteApplicationEvent)) {
this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(event).build());
}
}
根据acceptRemote方法来监听消息代理的输入通道,并根据事件类型和配置内容来确定是否要发布事件给我们之前分析的几个事件监听器来做对事件做具体的处理
根据acceptLocal方法用来监听本地的事件,针对事件来源是自己,并且事件类型不是AckRemoteApplicationEvent的内容通过消息代理的输出通道发送到总线上去。
主要acceptRemote就是从消息代理那里拿到事件,然后决定是否发布事件到监听器
@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()));
}
}
}
控制端点
发送到消息总线上用来触发各个节点的事件处理的动作
主要是由/bus/refresh和/bus/env实现,由于端点是由顶级基类Endpoint继承而来,所以我们一步步查看
Endpoint
接口中定义了监控端点需要暴露的一些有用信息,比如id、是否开启标志、是否开启敏感信息标识等
public interface Endpoint<T> {
String getId();
boolean isEnabled();
boolean isSensitive();
T invoke();
}
AbstractEndpoint
抽象类是对Endpoint的基础实现,在该类抽象类中引入了Environment接口对象,从而对接口中暴露信息的控制可以通过配置文件的方式来控制
public abstract class AbstractEndpoint<T> implements Endpoint<T>, EnvironmentAware {
private static final Pattern ID_PATTERN = Pattern.compile("\\w+");
private Environment environment;
private String id;
private final boolean sensitiveDefault;
private Boolean sensitive;
private Boolean enabled;
MvcEndPoint
接口,定义了Endpoint在MVC层的策略。在这里可以通过使用SpringMVC的@RequestMapping注解来定义端点暴露的接口地址
public interface MvcEndpoint {
ResponseEntity<Map<String, String>> DISABLED_RESPONSE = new ResponseEntity(Collections.singletonMap("message", "This endpoint is disabled"), HttpStatus.NOT_FOUND);
String getPath();
boolean isSensitive();
Class<? extends Endpoint> getEndpointType();
}
BusEndpoint
继承自AbstractEndpoint,默认id为bus,端点默认敏感标识为true
@ConfigurationProperties(
prefix = "endpoints.bus",
ignoreUnknownFields = false
)
public class BusEndpoint extends AbstractEndpoint<Collection<String>> {
public BusEndpoint() {
super("bus");
}
public Collection<String> invoke() {
return Collections.emptyList();
}
}
AbstractEndpoint
实现了mvc接口来暴露MVC层的接口,同时关联了BusEndpoint对象,大部分信息通过BusEndpoint获取信息,例如path
public class AbstractBusEndpoint implements MvcEndpoint {
private ApplicationEventPublisher context;
private BusEndpoint delegate;
private String appId;
public AbstractBusEndpoint(ApplicationEventPublisher context, String appId, BusEndpoint busEndpoint) {
this.context = context;
this.appId = appId;
this.delegate = busEndpoint;
}
protected String getInstanceId() {
return this.appId;
}
protected void publish(ApplicationEvent event) {
this.context.publishEvent(event);
}
public String getPath() {
return "/" + this.delegate.getId();
}
public boolean isSensitive() {
return this.delegate.isSensitive();
}
public Class<? extends Endpoint> getEndpointType() {
return this.delegate.getClass();
}
}
RefreshBusPoint
由于在父类的getPath是由id组成,所以完整的path为/bus/refresh
@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));
}
}
EnvironmentBusEndpoint
实现与RefreshBusEndpoint类似,提供map参数设定需要更新的配置信息
@ManagedResource
public class EnvironmentBusEndpoint extends AbstractBusEndpoint {
public EnvironmentBusEndpoint(ApplicationEventPublisher context, String id, BusEndpoint delegate) {
super(context, id, delegate);
}
@RequestMapping(
value = {"env"},
method = {RequestMethod.POST}
)
@ResponseBody
@ManagedOperation
public void env(@RequestParam Map<String, String> params, @RequestParam(value = "destination",required = false) String destination) {
this.publish(new EnvironmentChangeRemoteApplicationEvent(this, this.getInstanceId(), destination, params));
}
}
7.总结
从kafka的控制台可以看到,通过消息中间件的功能来达到发布事件的作用。大致过程就是通过访问一个特定的URL也就是/actuator/bus-refresh的方式,发送了一个特定的消息到消息中间件的T
首先通过post请求访问/bus/refresh ,触发了一个事件通过publish发布了此事件,监听器捕获到这个事件进行rest的配置信息请求,并且bus监听了这些有关的事件,当发布了一个刷新事件,则会发送事件消息到代理服务器,并且代理服务器把消息发送到所有订阅了信息的实例,消息里面包括了事件类型、服务范围等,由destination确定。所有实例因为订阅了topic,所以接受到消息后由于有一个@StreamListener监听了消息代理的数据输入流,接受到事件信息后再发布事件,继续触发刷新rest请求。