Spring Cloud Bus 消息总线介绍

所有节点返回的结果都是 unknown,因为所有节点的配置中没有hangzhou这个 key。

Bus 内部提供了EnvironmentBusEndpoint这个 Endpoint 通过 message broker 用来新增/更新配置。

访问任意节点该 Endpoint 对应的 url: /actuator/bus-env?name=hangzhou&value=alibaba 进行配置项的新增(比如访问 node1 的url):

curl -X POST ‘http://localhost:10001/actuator/bus-env?name=hangzhou&value=alibaba’ -H ‘content-type: application/json’

然后再次访问所有节点/bus/env获取配置:

$ curl -X GET ‘http://localhost:10001/bus/env?key=hangzhou’

unknown%

$ curl -X GET ‘http://localhost:10002/bus/env?key=hangzhou’

unknown%

$ curl -X GET ‘http://localhost:10003/bus/env?key=hangzhou’

unknown%

$ curl -X GET ‘http://localhost:10004/bus/env?key=hangzhou’

unknown%

$ curl -X GET ‘http://localhost:10005/bus/env?key=hangzhou’

unknown%
$ curl -X POST ‘http://localhost:10001/actuator/bus-env?name=hangzhou&value=alibaba’ -H ‘content-type: application/json’

$ curl -X GET ‘http://localhost:10005/bus/env?key=hangzhou’

alibaba%

$ curl -X GET ‘http://localhost:10004/bus/env?key=hangzhou’

alibaba%

$ curl -X GET ‘http://localhost:10003/bus/env?key=hangzhou’

alibaba%

$ curl -X GET ‘http://localhost:10002/bus/env?key=hangzhou’

alibaba%

$ curl -X GET ‘http://localhost:10001/bus/env?key=hangzhou’

alibaba%

可以看到,所有节点都新增了一个 key 为hangzhou的配置,且对应的 value 是alibaba。这个配置项是通过 Bus 提供的 EnvironmentBusEndpoint 完成的。

这里引用 程序猿DD 画的一张图片,Spring Cloud Config 配合 Bus 完成所有节点配置的刷新来描述之前的实例(本文实例不是刷新,而是新增配置,但是流程是一样的):

Spring Cloud Bus 消息总线介绍_第1张图片

[](()2. 部分节点的配置修改


比如在 node1 上指定 d 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 estination 为 rocketmq-bus-node2 ( node2 配置了 spring.cloud.bus.id 为rocketmq-bus-node2:10002,可以匹配上) 进行配置的修改:

curl -X POST ‘http://localhost:10001/actuator/bus-env/rocketmq-bus-node2?name=hangzhou&value=xihu’ -H ‘content-type: application/json’

访问/bus/env 获取配置(由于在 node1 上发送消息,Bus 也会对发送方的节点 node1 进行配置修改):

~ ⌚

$ curl -X POST ‘http://localhost:10001/actuator/bus-env/rocketmq-bus-node2?name=hangzhou&value=xihu’ -H ‘content-type: application/json’

$ curl -X GET ‘http://localhost:10005/bus/env?key=hangzhou’

alibaba%

$ curl -X GET ‘http://localhost:10004/bus/env?key=hangzhou’

alibaba%

$ curl -X GET ‘http://localhost:10003/bus/env?key=hangzhou’

alibaba%

$ curl -X GET ‘http://localhost:10002/bus/env?key=hangzhou’

xihu%

$ curl -X GET ‘http://localhost:10001/bus/env?key=hangzhou’

xihu%

可以看到,只有 node1 和 node2 修改了配置,其余的 3 个节点配置未改变。

[](()Bus 的实现

============================================================================

[](()1. Bus 概念介绍


[](()1)事件

Bus 中定义了远程事件RemoteApplicationEvent,该事件继承了 Spring 的事件ApplicationEvent,而且它目前有 4 个具体的实现:

Spring Cloud Bus 消息总线介绍_第2张图片

  • EnvironmentChangeRemoteApplicationEvent:远程环境变更事件。主要用于接收一个 Map 类型的数据并更新到 Spring 上下文中 Environment 中的事件。文中的实例就是使用这个事件并配合 EnvironmentBusEndpoint 和 EnvironmentChangeListener 完成的。

  • AckRemoteApplicationEvent:远程确认事件。Bus 内部成功接收到远程事件后会发送回AckRemoteApplicationEvent确认事件进行确认。

  • RefreshRemoteApplicationEvent: 远程配置刷新事件。配合 @RefreshScope 以及所有的 @ConfigurationProperties注解修饰的配置类的动态刷新。

  • UnknownRemoteApplicationEvent:远程未知事件。Bus 内部消息体进行转换远程事件的时候如果发生异常会统一包装成该事件。

Bus 内部还存在一个非RemoteApplicationEvent事件 -SentApplicationEvent消息发送事件,配合 Trace 进行远程消息发送的记录。

这些事件会配合ApplicationListener进行操作,比如EnvironmentChangeRemoteApplicationEvent配了EnvironmentChangeListener进行配置的新增/修改:

public class EnvironmentChangeListener

implements ApplicationListener {

private static Log log = LogFactory.getLog(EnvironmentChangeListener.class);

@Autowired

private EnvironmentManager env;

@Override

public void onApplicationEvent(EnvironmentChangeRemoteApplicationEvent event) {

Map values = event.getValues();

log.info("Received remote environment change request. Keys/values to update "

  • values);

for (Map.Entry entry : values.entrySet()) {

env.setProperty(entry.getKey(), entry.getValue());

}

}

}

收到其它节点发送来EnvironmentChangeRemoteApplicationEven事件之后调用EnvironmentManager#setProperty进行配置的设置,该方法内部针对每一个配置项都会发送一个EnvironmentChangeEvent事件,然后被ConfigurationPropertiesRebinder所监听,进行 rebind 操作新增/更新配置。

[](()2)Actuator Endpoint

Bus 内部暴露了 2 个 Endpoint,分别是EnvironmentBusEndpoint和RefreshBusEndpoint,进行配置的新增/修改以及全局配置刷新。它们对应的 Endpoint id 即 url 是 bus-env和bus-refresh。

[](()3)配置

Bus 对于消息的发送必定涉及到 Topic、Group 之类的信息,这些内容都被封装到了BusProperties中,其默认的配置前缀为spring.cloud.bus,比如:

  • spring.cloud.bus.refresh.enabled用于开启/关闭全局刷新的 Listener。

  • spring.cloud.bus.env.enabled 用于开启/关闭配置新增/修改的 Endpoint。

  • spring.cloud.bus.ack.enabled 用于开启开启/关闭AckRemoteApplicationEvent事件的发送。

  • spring.cloud.bus.trace.enabled 用于开启/关闭息记录 Trace 的 Listener。

消息发送涉及到的 Topic 默认用的是springCloudBus,可以配置进行修改,Group 可以设置成广播模式或使用 UUID 配合 offset 为 lastest 的模式。

每个 Bus 应用都有一个对应的 Bus id,官方取值方式较复杂:

KaTeX parse error: Expected ‘}’, got ‘EOF’ at end of input: …plication.name:{spring.application.name:application}}:KaTeX parse error: Expected ‘}’, got ‘EOF’ at end of input: …instance_index:{spring.application.index:KaTeX parse error: Expected ‘}’, got ‘EOF’ at end of input: …al.server.port:{server.port:0}}}}:KaTeX parse error: Expected ‘}’, got ‘EOF’ at end of input: …on.instance_id:{random.value}}

建议手动配置 Bus id,因为 Bus 远程事件中的 destination 会根据 Bus id 进行匹配:

spring.cloud.bus.id= s p r i n g . a p p l i c a t i o n . n a m e − {spring.application.name}- spring.application.name{server.port}

[](()2. Bus 底层分析


Bus 的底层分析无非牵扯到这几个方面:

  • 消息是如何发送的

  • 消息是如何接收的

  • destination 是如何匹配的

  • 远程事件收到后如何触发下一个 action

BusAutoConfiguration自动化配置类被@EnableBinding(SpringCloudBusClient.class)所修饰。

@EnableBinding的用法在文章[《Spring Cloud Stream 体系及原理介绍》](()中已经说明,且它的 value 为SpringCloudBusClient.class,会在SpringCloudBusClient中基于代理创建出 input 和 output 的DirectChannel:

public interface SpringCloudBusClient {

String INPUT = “springCloudBusInput”;

String OUTPUT = “springCloudBusOutput”;

@Output(SpringCloudBusClient.OUTPUT)

MessageChannel springCloudBusOutput();

@Input(SpringCloudBusClient.INPUT)

SubscribableChannel springCloudBusInput();

}

springCloudBusInput 和 springCloudBusOutput 这两个 Binding 的属性可以通过配置文件进行修改(比如修改 topic):

spring.cloud.stream.bindings:

springCloudBusInput:

destination: my-bus-topic

springCloudBusOutput:

destination: my-bus-topic

消息的接收和发送:

// BusAutoConfiguration

@EventListener(classes = RemoteApplicationEvent.class) // 1

public void acceptLocal(RemoteApplicationEvent event) {

if (this.serviceMatcher.isFromSelf(event)

&& !(event instanceof AckRemoteApplicationEvent)) { // 2

this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(event).build()); // 3

你可能感兴趣的:(Java,经验分享,架构,java)