By Luoye
A series on how to use RocketMQ in the Spring ecosystem:
Spring Cloud Bus is positioned to be the message bus of the Spring Cloud system, which uses the message broker to connect all the nodes of the distributed system. The official Reference document of Bus is not rich in information; it does not even include a picture.
The latest code structure of Spring Cloud Bus is listed below:

Before we analyze how Bus is implemented, let’s take a look at two simple examples of how to use Spring Cloud Bus.
The example for Bus is relatively simple because the default configuration is already available in the AutoConfiguration layer of Bus. You only need to introduce the Spring Cloud Stream framework corresponding to the message middleware and dependencies of Spring Cloud Bus. After that, all running applications will use the same Topic to receive and send messages.
The Demo for Bus has been uploaded to GitHub. This Demo simulates the startup of five nodes. When you add a new configuration item to any of these five nodes, the configuration item will be added to all of them.
Demo Address: https://github.com/fangjian0423/rocketmq-binder-demo/tree/master/rocketmq-bus-demo
Access the Controller provided by any node to obtain the configuration address (the key is hangzhou):
curl -X GET 'http://localhost:10001/bus/env?key=hangzhou'
The results returned by all nodes are unknown because the hangzhou key does not exist in the configuration of any node.
Bus has a built-in EnvironmentBusEndpoint, which is used to add or update configuration through the message broker.
To add a new configuration item to a node, access the URL of the Endpoint of any node, which is /actuator/bus-env?name=hangzhou&value=Alibaba. For example, access the URL of node1:
curl -X POST 'http://localhost:10001/actuator/bus-env?name=hangzhou&value=alibaba' -H 'content-type: application/json'
Then, access the /bus/env of all nodes to obtain the configuration:
$ 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%
A new configuration item has been added to all nodes, which is key=Hangzhou. The value is alibaba. The configuration item is added through the EnvironmentBusEndpoint provided by Bus.
Here’s a picture that was drawn by ChengxuyuanDD to show how Spring Cloud Config (in conjunction with Bus) refreshes all node configurations to describe the previous examples. The examples in this article are not refreshing (but adding) new configurations. The process is all the same:

For example, set the destination to rocketmq-bus-node2 on node1 (node2 can be matched as spring.cloud.bus.id is set to rocketmq-bus-node2:10002) and modify the configuration:
curl -X POST 'http://localhost:10001/actuator/bus-env/rocketmq-bus-node2?name=hangzhou&value=xihu' -H 'content-type: application/json'
Access /bus/env to obtain the configuration. The message is sent from node1, so Bus also modifies the configuration of 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%
Only the configuration of node1 and node2 is modified. The remaining three nodes remain unchanged.
A remote event RemoteApplicationEvent is defined in Bus. This event inherits the event ApplicationEvent of Spring and has four implementations:

EnvironmentChangeRemoteApplicationEvent: The remote environment change event. This event mainly receives the Map<String,String> data and inserts the data into the Environment events of Spring context. The example in this article is completed by using this event in conjunction with EnvironmentBusEndpoint and EnvironmentChangeListener.AckRemoteApplicationEvent: The remote acknowledgement event. Bus will send an AckRemoteApplicationEvent event after receiving a remote event successfully.RefreshRemoteApplicationEvent: The remote configuration refreshing event. This event is used in conjunction with the @RefreshScope annotation and all @ConfigurationProperties annotations to dynamically refresh the configuration class annotated by these annotations.UnknownRemoteApplicationEvent: The remote unknown event. If an exception is thrown when internal messages of Bus are converted into remote events, the exception will be encapsulated as this event.Bus also has a non-RemoteApplicationEvent event, SentApplicationEvent, a message sending event. This event is used in conjunction with Trace to record the sending of remote messages.
All these events are used in conjunction with ApplicationListener. For example, EnvironmentChangeRemoteApplicationEvent is used in conjunction with EnvironmentChangeListener to add or update the configuration:
public class EnvironmentChangeListener
implements ApplicationListener<EnvironmentChangeRemoteApplicationEvent> {
private static Log log = LogFactory.getLog(EnvironmentChangeListener.class);
@Autowired
private EnvironmentManager env;
@Override
public void onApplicationEvent(EnvironmentChangeRemoteApplicationEvent event) {
Map<String, String> values = event.getValues();
log.info("Received remote environment change request. Keys/values to update "
+ values);
for (Map.Entry<String, String> entry : values.entrySet()) {
env.setProperty(entry.getKey(), entry.getValue());
}
}
}
Call the EnvironmentManager#setProperty method to set the configuration upon receiving the EnvironmentChangeRemoteApplicationEvent event from other nodes. This method sends an EnvironmentChangeEvent event for each configuration item, which is listened for by ConfigurationPropertiesRebinder to perform the rebind operation for adding or updating the configuration.
Bus provides two Endpoints (EnvironmentBusEndpoint and RefreshBusEndpoint) to add or update the configuration or refresh the global configuration. Their corresponding Endpoint IDs (or URLs) are bus-env and bus-refresh, respectively.
Message sending in Bus inevitably involves the Topic and Group information. Such content has been encapsulated in BusProperties. The default prefix is spring.cloud.bus. For example:
spring.cloud.bus.refresh.enabled is used to enable or disable the Listener that listens for the global refresh event.spring.cloud.bus.env.enabled is used to enable or disable the Endpoint for adding or updating the configuration.spring.cloud.bus.ack.enabled is used to enable or disable the sending of the AckRemoteApplicationEvent event.spring.cloud.bus.trace.enabled is used to enable or disable the Listener that listens for the Trace.The default Topic for sending messages is springCloudBus, which can be changed through configuration. You can set the Group to the broadcasting mode or set it to the latest mode using UUID in conjunction with the offset.
Each Bus application has a unique Bus ID. The official format of the Bus ID is complex:
${vcap.application.name:${spring.application.name:application}}:${vcap.application.instance_index:${spring.application.index:${local.server.port:${server.port:0}}}}:${vcap.application.instance_id:${random.value}}
We recommend manual Bus ID setting because the destinations of Bus remote events need to match the Bus ID:
spring.cloud.bus.id=${spring.application.name}-${server.port}
The underlying analysis of Bus involves the following questions:
The BusAutoConfiguration class is annotated by the @EnableBinding(SpringCloudBusClient.class) annotation.
The introduction of @EnableBinding usage is in the article Introduction to the Spring Cloud Stream System. Its value is SpringCloudBusClient.class. It will create the DirectChannel for the input and output in SpringCloudBusClient based on the proxy:
public interface SpringCloudBusClient {
String INPUT = "springCloudBusInput";
String OUTPUT = "springCloudBusOutput";
@Output(SpringCloudBusClient.OUTPUT)
MessageChannel springCloudBusOutput();
@Input(SpringCloudBusClient.INPUT)
SubscribableChannel springCloudBusInput();
}
The Binding properties springCloudBusInput and springCloudBusOutput can be changed by modifying the configuration file (for example, by modifying the topic):
spring.cloud.stream.bindings:
springCloudBusInput:
destination: my-bus-topic
springCloudBusOutput:
destination: my-bus-topic
Message receiving and sending:
// 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
}
}
@StreamListener(SpringCloudBusClient.INPUT) // 4
public void acceptRemote(RemoteApplicationEvent event) {
if (event instanceof AckRemoteApplicationEvent) {
if (this.bus.getTrace().isEnabled() && !this.serviceMatcher.isFromSelf(event)
&& this.applicationEventPublisher != null) { // 5
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) { // 6
if (!this.serviceMatcher.isFromSelf(event)) { // 7
this.applicationEventPublisher.publishEvent(event);
}
if (this.bus.getAck().isEnabled()) { // 8
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) { // 9
// 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()));
}
}
RemoteApplicationEvent. For example, the bus-env sends the EnvironmentChangeRemoteApplicationEvent locally, and the bus-refresh sends the RefreshRemoteApplicationEvent locally. These events will be listened to here.
AckRemoteApplicationEvent. (Otherwise, Bus will get stuck in an endless loop, receiving and sending messages repeatedly.) Then, verify that the event was sent by the application itself. If both conditions are met, perform Step 3.MessageChannel (the Binding name is springCloudBusOutput) created by Spring Cloud Stream to send the message to the broker.MessageChannel (the Binding name is springCloudBusInput) created by Spring Cloud Stream, and the message received is a remote message.AckRemoteApplicationEvent, the trace function is enabled, and the event is not sent by the application itself, which indicates that the event is sent by another application. The local sending of AckRemoteApplicationEvent indicates that the application acknowledges that it has received the remote event sent by another application. Thus, the process ends.AckRemoteApplicationEvent is enabled, create an AckRemoteApplicationEvent and send this event remotely and locally. (The reason for local sending is that the AckRemoteApplicationEvent was not sent in Step 5, which means that the application acknowledges itself. Remote sending is to tell other applications that the application has received the message.)SentApplicationEvent locally.After bus-env is triggered, the EnvironmentChangeListener of all nodes will detect the configuration change, and controllers of all these nodes will print the following information:
o.s.c.b.event.EnvironmentChangeListener : Received remote environment change request. Keys/values to update {hangzhou=alibaba}
If you listen for AckRemoteApplicationEvent on the current node, you will receive information from all nodes. For example, the AckRemoteApplicationEvent listened for on node5 is listed below:
ServiceId [rocketmq-bus-node5:10005] listeners on {"type":"AckRemoteApplicationEvent","timestamp":1554124670484,"originService":"rocketmq-bus-node5:10005","destinationService":"**","id":"375f0426-c24e-4904-bce1-5e09371fc9bc","ackId":"750d033f-356a-4aad-8cf0-3481ace8698c","ackDestinationService":"**","event":"org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent"}
ServiceId [rocketmq-bus-node5:10005] listeners on {"type":"AckRemoteApplicationEvent","timestamp":1554124670184,"originService":"rocketmq-bus-node1:10001","destinationService":"**","id":"91f06cf1-4bd9-4dd8-9526-9299a35bb7cc","ackId":"750d033f-356a-4aad-8cf0-3481ace8698c","ackDestinationService":"**","event":"org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent"}
ServiceId [rocketmq-bus-node5:10005] listeners on {"type":"AckRemoteApplicationEvent","timestamp":1554124670402,"originService":"rocketmq-bus-node2:10002","destinationService":"**","id":"7df3963c-7c3e-4549-9a22-a23fa90a6b85","ackId":"750d033f-356a-4aad-8cf0-3481ace8698c","ackDestinationService":"**","event":"org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent"}
ServiceId [rocketmq-bus-node5:10005] listeners on {"type":"AckRemoteApplicationEvent","timestamp":1554124670406,"originService":"rocketmq-bus-node3:10003","destinationService":"**","id":"728b45ee-5e26-46c2-af1a-e8d1571e5d3a","ackId":"750d033f-356a-4aad-8cf0-3481ace8698c","ackDestinationService":"**","event":"org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent"}
ServiceId [rocketmq-bus-node5:10005] listeners on {"type":"AckRemoteApplicationEvent","timestamp":1554124670427,"originService":"rocketmq-bus-node4:10004","destinationService":"**","id":"1812fd6d-6f98-4e5b-a38a-4b11aee08aeb","ackId":"750d033f-356a-4aad-8cf0-3481ace8698c","ackDestinationService":"**","event":"org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent"}
Now, let’s go back to the four questions from the beginning of this section:
springCloudBustopic using the BusAutoConfiguration#acceptLocal method through Spring Cloud Stream.springCloudBustopic using the BusAutoConfiguration#acceptRemote method through Spring Cloud Stream.BusAutoConfiguration#acceptRemote method.RemoteApplicationEvent through the Spring event mechanism. (For example, the EnvironmentChangeListener receives the EnvironmentChangeRemoteApplicationEvent, and the RefreshListener receives the RefreshRemoteApplicationEvent.)Spring Cloud Bus does not have too much content. However, you need to understand the Spring Cloud Stream system and the Spring event mechanism to understand how Spring Cloud Bus processes local and remote events.
Currently, Bus provides only a few built-in remote events, while most events are related to configuration. We can use the RemoteApplicationEvent in conjunction with the @RemoteApplicationEventScan annotation to build our own microservice message system.
Fang Jian (Luoye), GitHub ID: @fangjian0423 Is an open-source fan, Alibaba Cloud Senior Development Engineer, and Alibaba Cloud EDAS Developer. Fang Jian is one of the owners of the open-source Spring Cloud Alibaba project.
Understand the XA Mode of Distributed Transaction in Six Figures
626 posts | 54 followers
FollowAlibaba Clouder - May 17, 2019
Alibaba Developer - August 19, 2021
Alibaba Clouder - May 17, 2019
Alibaba Clouder - March 27, 2019
Alibaba Clouder - November 18, 2019
Alibaba Cloud Native Community - November 10, 2021
626 posts | 54 followers
Follow
Function Compute
Alibaba Cloud Function Compute is a fully-managed event-driven compute service. It allows you to focus on writing and uploading code without the need to manage infrastructure such as servers.
Learn More
ApsaraMQ for RocketMQ
ApsaraMQ for RocketMQ is a distributed message queue service that supports reliable message-based asynchronous communication among microservices, distributed systems, and serverless applications.
Learn More
Managed Service for Prometheus
Multi-source metrics are aggregated to monitor the status of your business and services in real time.
Learn More
Elastic High Performance Computing Solution
High Performance Computing (HPC) and AI technology helps scientific research institutions to perform viral gene sequencing, conduct new drug research and development, and shorten the research and development cycle.
Learn MoreMore Posts by Alibaba Cloud Native Community