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
507 posts | 48 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
507 posts | 48 followers
FollowAlibaba 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 MoreMulti-source metrics are aggregated to monitor the status of your business and services in real time.
Learn MoreApsaraMQ for RocketMQ is a distributed message queue service that supports reliable message-based asynchronous communication among microservices, distributed systems, and serverless applications.
Learn MoreHigh 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