Building an Efficient MQTT Cluster System π
Introduction π§
In modern distributed systems, the clustered deployment of MQTT Brokers is a key method to enhance system availability β , scalability π, and load balancing capabilities π.
The <span>cluster-plugin</span> of smart-mqtt implements an efficient and scalable cluster coordination system π by combining the HTTP client of the Feat framework with the Server-Sent Events (SSE) mechanism. This plugin supports deployment modes for Core nodes and Worker nodes, enabling node discovery, state synchronization, and message forwarding π‘ through HTTP communication.
This article will delve into its technical details based on the source code implementation, including node coordination mechanisms, message distribution and reception processes, queue strategy handling, etc. π‘, helping developers better understand its design principles and deployment methods.
1. Overall Architecture Overview π§±
1.1 Core Component:<span>Coordinator</span>
The core component of the plugin is the <span>Coordinator</span> class, which inherits from <span>AsyncTask</span> and is responsible for starting two key threads:
- β’ Distributor (Distribution Thread): Responsible for consuming messages from the message bus (
<span>MessageBus</span>) and distributing them to other nodes in the cluster π€. This is specifically implemented within<span>Distributor</span>. - β’ Receiver (Receiving Thread): Responsible for receiving messages from other nodes and publishing them to the local message bus π₯. This is specifically implemented within
<span>Receiver</span>.
1.2 Configuration Management:<span>PluginConfig</span>
Configuration is loaded through <span>PluginConfig</span>, which includes the following key parameters:
- β’ Whether it is a Core node (Core) π
- β’ Listening address and port π
- β’ Queue length and strategy βοΈ
- β’ List of cluster node addresses π
1.3 Startup Process π¦
During plugin initialization, <span>ClusterPlugin</span> will start the <span>Coordinator</span> thread. If the node is Core, it will start the HTTP server provided by Feat, listening for requests on the <span>/cluster</span> path to handle status checks, message pushing, and subscription operations π‘.
// ... existing code ...
if (pluginConfig.isCore()) {
httpServer = FeatCloud.cloudServer(cloudOptions -> cloudOptions.registerBean("mqttSession", coordinator.mqttSession).registerBean("brokerContext", brokerContext).host(pluginConfig.getHost()).port(pluginConfig.getPort()).debug(true)).listen();
}
// ... existing code ...
2. Node Coordination and Discovery Mechanism π€
<span>Coordinator</span> maintains a list of <span>ClusterClient</span>, each corresponding to a cluster node (loaded via <span>pluginConfig.getClusters()</span>).
2.1 Node Status Detection π΅οΈβοΈ
Each <span>ClusterClient</span> periodically sends GET requests to the <span>/cluster/status</span> interface using Feat’s <span>HttpClient</span> to check if the node is available:
// ... existing code ...
clusterClient.httpClient.get("/cluster/status").onSuccess(httpResponse -> {
LOGGER.info("check node status success.");
clusterClient.httpEnable = true;
clusterClient.checkPending = false;
}).onFailure(throwable -> {
clusterClient.httpEnable = false;
clusterClient.checkPending = false;
LOGGER.error("check node status error", throwable);
}).submit();
// ... existing code ...
β Success: Marked as available, start SSE subscriptionβ Failure: Marked as unavailable, pause connection
2.2 SSE Subscription Mechanism π»
- β’ Core Node: Starts SSE subscriptions for each available ClusterClient to receive messages from other nodes π₯.
- β’ Worker Node: Connects to only one Core node and automatically releases and reallocates when its connection fails π.
Subscriptions are initiated via Feat’s <span>HttpClient</span> by sending a POST request to <span>/cluster/subscribe/{nodeType}/{access_token}</span> and using a custom <span>ClusterMessageStream</span> to handle SSE events.
3. Message Distribution Mechanism (Distributor) π€
The Distributor thread consumes messages from the <span>MessageBus</span> and distributes them based on node type:
- β’ Core Node: Sends messages via HTTP POST to the
<span>/cluster/put/core</span>interface of other Core nodes and publishes the event<span>CLIENT_DIRECT_TO_CORE_BROKER</span>to notify local Workers π’. - β’ Worker Node: Sends messages to the
<span>/cluster/put/worker</span>interface of its connected Core node π€.
// ... existing code ...
if (pluginConfig.isCore()) {
for (ClusterClient clusterClient : clients) {
if (clusterClient.httpEnable) {
// coreθηΉεεζΆζ―θ³ιηΎ€ε
Άδ»coreθηΉ
clusterClient.httpClient.post("/cluster/put/core").header(header -> header.keepalive(true).set("access_token", ACCESS_TOKEN).setContentLength(message.getPayload().length).set(ClusterController.HEADER_TOPIC, message.getTopic().getTopic())).body(requestBody -> requestBody.write(message.getPayload())).onFailure(throwable -> {
clusterClient.httpEnable = false;
LOGGER.error("send message to cluster error", throwable);
}).onSuccess(httpResponse -> {
LOGGER.info("send message to cluster success");
}).submit();
}
}
brokerContext.getEventBus().publish(ClusterPlugin.CLIENT_DIRECT_TO_CORE_BROKER, message);
} elseif (workerClient != null) {
workerClient.httpClient.post("/cluster/put/worker").header(header -> header.keepalive(true).set("access_token", ACCESS_TOKEN).setContentLength(message.getPayload().length).set(ClusterController.HEADER_TOPIC, message.getTopic().getTopic())).body(requestBody -> requestBody.write(message.getPayload())).onFailure(throwable -> {
workerClient.httpEnable = false;
LOGGER.error("send message to cluster error", throwable);
}).submit();
}
// ... existing code ...
3.1 Queue Strategy π¦
Messages are first placed into the <span>distributorQueue</span> (ArrayBlockingQueue), with its capacity determined by <span>queueLength</span>. If the queue is full:
- β’ Strategy 0: Discard the latest message β (default)
- β’ Strategy 1: Discard the oldest message β
private void offer(Message message, ArrayBlockingQueue<Message> clusterMessageQueue) {
if (pluginConfig.getQueuePolicy() == QUEUE_POLICY_DISCARD_NEWEST) {
boolean suc = clusterMessageQueue.offer(message);
if (!suc) {
LOGGER.warn("queue is full, discard message: {}", message);
}
} else {
while (!clusterMessageQueue.offer(message)) {
Message discard = clusterMessageQueue.poll();
if (discard != null) {
LOGGER.warn("queue is full, discard message: {}", discard);
}
}
}
}
4. Message Reception Mechanism (Receiver) π₯
The Receiver thread consumes messages from the <span>receiverQueue</span> and publishes them to the local <span>MessageBus</span>.
4.1 SSE Message Subscription π»
By sending a request to <span>/cluster/subscribe/{nodeType}/{access_token}</span>, subscriptions to SSE message streams are initiated. The custom <span>ClusterMessageStream</span> parses the <span>topic</span>, <span>payload</span>, and <span>retained</span> flags in the event and constructs a <span>Message</span> object:
@Override
public void onEvent(HttpResponse httpResponse, String topic, byte[] payload, boolean retained) {
if (!enabled) {
LOGGER.warn("cluster-plugin-consume-message-error");
return;
}
Message message = new Message(brokerContext.getOrCreateTopic(topic), MqttQoS.AT_MOST_ONCE, payload, retained);
offer(message, receiverQueue);
}
4.2 Message Broadcasting Mechanism π’
After receiving a message, the Core node further broadcasts it to all Worker nodes π, implemented through <span>brokerContext.getEventBus().publish</span>.
5. Message Format and Parsing π
The plugin uses a custom message format for serialization and deserialization:
topic:<topic_name>
retain:
payload:<length> <payload_bytes>
- β’
<span>topic</span>line indicates the message topic π§Ύ - β’
<span>retain</span>line indicates whether it is a retained message (optional) π - β’
<span>payload</span>line contains the message body length and content π¦
public byte[] toBytes(Message message) {
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(message.getPayload().length + 32);
try {
byteOutputStream.write(ClusterMessageStream.TAG_TOPIC);
byteOutputStream.write(':');
byteOutputStream.write(message.getTopic().getTopic().getBytes());
byteOutputStream.write('\n');
if (message.isRetained()) {
byteOutputStream.write(ClusterMessageStream.TAG_RETAIN);
byteOutputStream.write(':');
byteOutputStream.write('\n');
}
byteOutputStream.write(ClusterMessageStream.TAG_PAYLOAD);
byteOutputStream.write(':');
byteOutputStream.write((message.getPayload().length + " ").getBytes());
byteOutputStream.write(message.getPayload());
byteOutputStream.write('\n');
byteOutputStream.write('\n');
} catch (IOException e) {
e.printStackTrace();
}
return byteOutputStream.toByteArray();
}
6. Configuration and Deployment Recommendations βοΈ
6.1 Plugin Configuration (<span>plugin.yaml</span>)
core: true # Whether it is a core node, true=core node, false=worker node
host: 0.0.0.0 # Cluster service listening address, effective only when core is true
port: 8884 # Cluster service listening port, effective only when core is true
queueLength: 1024 # Message queue length
queuePolicy: 0 # Queue strategy (0=discard newest, 1=discard oldest)
clusters: # List of cluster node addresses
- http://core1:8884
- http://core2:8884
6.2 Deployment Considerations π
- β’ Startup Order: Start Core nodes first, then Worker nodes, to ensure node discovery and connection are normal π.
- β’ Network Connectivity: Ensure that nodes can communicate through the configured HTTP ports π.
- β’ Queue Tuning: Adjust
<span>queueLength</span>and<span>queuePolicy</span>based on business load to avoid message loss or backlog βοΈ.
7. Conclusion π§Ύ
<span>cluster-plugin</span> implements an efficient and flexible MQTT cluster architecture π through the HTTP client and SSE mechanism provided by the Feat framework. Its core design includes:
- β’ Asynchronous task scheduling (Distributor/Receiver) π
- β’ Queue management and strategy control βοΈ
- β’ Event-driven message distribution and reception π’
- β’ Dynamic node discovery and state synchronization mechanism π
This plugin is suitable for high availability and high concurrency MQTT cluster scenarios πΌ. Developers can adjust configuration parameters and optimize queue strategies according to actual needs to further enhance system performance and stability π.
Gitee AddressοΌsmart-mqtt/plugins/cluster-plugin[1]Documentation ReferenceοΌFeat Framework Documentation[2]
If further customization or integration is needed, feel free to participate in open-source community discussions and contributions π¬β¨
Reference Links
<span>[1]</span> smart-mqtt/plugins/cluster-plugin: https://gitee.com/smart-mqtt/smart-mqtt<span>[2]</span> Feat Framework Documentation: https://smartboot.tech/feat