RocketMQ 一主两从集群搭建与 SpringBoot 集成完整方案
一、整体架构与前提说明
本方案采用 1 主 2 从(Master + Slave1 + Slave2)异步复制模式部署。该模式的特点是:Master 宕机后消费者仍可从 Slave 消费,对应用透明;但 Master 磁盘损坏时可能丢失少量消息。若需要零丢失,可在 Broker 配置中将刷盘和复制策略调整为同步。
组件清单:3 台服务器(或 3 个虚拟机),至少 2 核 4G 内存,JDK 1.8+,RocketMQ 4.9.x。另需 1 台用于部署 Dashboard 和监控组件(可与上述服务器复用)。
组件角色:
· NameServer:路由注册中心,无状态,建议部署 2 台以上
· Broker-Master:消息读写主节点
· Broker-Slave1 / Slave2:数据备份节点,Master 宕机后可接管消费
二、集群搭建
2.1 下载与解压(每台机器都执行)
bash
wget https://archive.apache.org/dist/rocketmq/4.9.7/rocketmq-all-4.9.7-bin-release.zip
unzip rocketmq-all-4.9.7-bin-release.zip
mv rocketmq-all-4.9.7-bin-release /usr/local/rocketmq
新手注意:RocketMQ 默认启动内存为 4G+2G,小内存机器必须修改。编辑 bin/runserver.sh 和 bin/runbroker.sh,将 -Xms4g -Xmx4g 等参数改为 -Xms512m -Xmx512m。
2.2 启动 NameServer(在 2 台机器上执行)
bash
nohup sh bin/mqnamesrv &
tail -f ~/logs/rocketmqlogs/namesrv.log
# 看到 "The Name Server boot success..." 即成功
2.3 配置 Broker
Master(broker-a,服务器 A) --- 编辑 conf/broker-a.properties:
properties
brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0 # 0 表示 Master
brokerRole = ASYNC_MASTER # 异步复制主节点
flushDiskType = ASYNC_FLUSH # 异步刷盘
deleteWhen = 04
fileReservedTime = 48
brokerIP1 = 192.168.1.10 # 改为本机实际 IP
namesrvAddr = 192.168.1.10:9876;192.168.1.11:9876
defaultTopicQueueNums = 4
autoCreateTopicEnable = false # 生产环境建议关闭
listenPort = 10911
Slave1(broker-a-s1,服务器 B):
properties
brokerName = broker-a # 必须与 Master 相同
brokerId = 1 # 非 0 表示 Slave
brokerRole = SLAVE
brokerIP1 = 192.168.1.11
namesrvAddr = 192.168.1.10:9876;192.168.1.11:9876
Slave2(broker-a-s2,服务器 C):
properties
brokerName = broker-a
brokerId = 2
brokerRole = SLAVE
brokerIP1 = 192.168.1.12
namesrvAddr = 192.168.1.10:9876;192.168.1.11:9876
关键说明:Master 与 Slave 的关联通过相同的 brokerName 和不同的 brokerId 定义,brokerId=0 为 Master,非 0 为 Slave。一个 Slave 只能对应一个 Master。
2.4 启动 Broker(按顺序:先 Master,再 Slave)
bash
# 服务器 A(Master)
nohup sh bin/mqbroker -c conf/broker-a.properties &
# 服务器 B(Slave1)
nohup sh bin/mqbroker -c conf/broker-a-s1.properties &
# 服务器 C(Slave2)
nohup sh bin/mqbroker -c conf/broker-a-s2.properties &
# 验证
tail -f ~/logs/rocketmqlogs/broker.log
# 看到 "boot success" 即成功
新手常见坑:如果 Slave 一直启动失败,检查 brokerIP1 是否填了实际 IP,以及 10912 端口(HA 复制端口)是否开放。
三、SpringBoot 集成
3.1 添加依赖
xml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
建议使用 2.3.x 版本,可同时兼容 Spring Boot 2 和 Spring Boot 3。
3.2 配置文件(application.yml)
yaml
rocketmq:
name-server: 192.168.1.10:9876;192.168.1.11:9876
producer:
group: my-producer-group
send-message-timeout: 5000 # 发送超时 5 秒
retry-times-when-send-failed: 3 # 同步发送失败重试次数
3.3 发送普通消息
java
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void sendMessage(String topic, String body) {
SendResult result = rocketMQTemplate.syncSend(topic,
MessageBuilder.withPayload(body).build());
if (result.getSendStatus() != SendStatus.SEND_OK) {
log.error("消息发送失败: {}", result);
}
}
关键点:必须使用 syncSend(同步发送)并检查 SendStatus,异步发送无法确认 Broker 是否真正收到消息。
3.4 消费消息
java
@Component
@RocketMQMessageListener(
topic = "my-topic",
consumerGroup = "my-consumer-group",
messageModel = MessageModel.CLUSTERING,
consumeMode = ConsumeMode.CONCURRENTLY
)
public class MyConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
// 业务逻辑,抛异常则触发重试
System.out.println("收到消息: " + message);
}
}
四、半事务消息(事务消息)
事务消息的核心机制:生产者先发送一条对消费者不可见的半消息 → 执行本地事务 → 根据本地事务结果提交或回滚半消息。若 Broker 长时间未收到确认,会回调生产者回查事务状态。
4.1 发送事务消息
java
@Service
public class TransactionalProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void sendInTransaction(String topic, String body) {
TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
topic,
MessageBuilder.withPayload(body).build(),
null // 额外参数
);
log.info("事务消息发送结果: {}", result.getLocalTransactionState());
}
}
4.2 事务监听器(关键实现)
java
@RocketMQTransactionListener
public class TransactionListenerImpl implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
// 执行本地事务(如数据库操作)
// orderService.createOrder(...);
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
log.error("本地事务执行失败", e);
return RocketMQLocalTransactionState.ROLLBACK;
}
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
// Broker 回查时调用:查数据库确认本地事务是否成功
// boolean success = orderService.checkOrderExists(orderId);
// return success ? COMMIT : ROLLBACK;
return RocketMQLocalTransactionState.COMMIT;
}
}
新手提醒:checkLocalTransaction 方法必须实现幂等查询逻辑,Broker 可能多次回查同一消息。
五、顺序消息
顺序消息的核心约束:发送时通过 Sharding Key 将同一业务实体的消息路由到同一个队列,消费时对同一队列单线程消费。
5.1 顺序发送
java
public void sendOrderly(String topic, String shardingKey, String body) {
// shardingKey 例如订单ID,确保同一订单的消息进入同一队列
rocketMQTemplate.syncSendOrderly(topic,
MessageBuilder.withPayload(body).build(),
shardingKey);
}
顺序消息只支持同步发送,不支持异步发送。
5.2 顺序消费
java
@Component
@RocketMQMessageListener(
topic = "order-topic",
consumerGroup = "order-consumer-group",
consumeMode = ConsumeMode.ORDERLY // 关键:顺序消费模式
)
public class OrderlyConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
// 同一队列的消息会单线程按顺序处理
}
}
注意:同一个 Group ID 不要同时用于顺序消息和无序消息,否则顺序无法保证。
六、消息可靠性配置
6.1 消息不丢失的三层保障
层面 配置/措施 说明
生产者 同步发送 + 重试 检查 SendStatus,失败自动重试
Broker flushDiskType=SYNC_FLUSH + brokerRole=SYNC_MASTER 同步刷盘 + 同步复制,零丢失
消费者 手动确认 + 重试 业务异常返回 RECONSUME_LATER
生产环境建议:核心业务使用 SYNC_FLUSH + SYNC_MASTER,非核心业务可用 ASYNC_FLUSH 提升吞吐。
6.2 消息幂等(防重复消费)
RocketMQ 保证"至少一次"投递,重复消费不可避免,必须在消费端做幂等:
java
@RocketMQMessageListener(topic = "order-topic", consumerGroup = "order-group")
public class IdempotentConsumer implements RocketMQListener<MessageExt> {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public void onMessage(MessageExt message) {
String msgId = message.getMsgId();
// Redis SETNX 去重
Boolean first = redisTemplate.opsForValue()
.setIfAbsent("mq:msg:" + msgId, "1", 24, TimeUnit.HOURS);
if (Boolean.FALSE.equals(first)) {
return; // 已处理,跳过
}
// 处理业务...
}
}
七、主从切换
7.1 传统主从模式(手动切换)
一主两从(ASYNC_MASTER)模式下,Master 宕机后 不会自动切换。消费者可以从 Slave 继续消费已有消息,但新的消息无法写入(无 Master 可写)。恢复方式:手动将 Slave 的 brokerRole 改为 ASYNC_MASTER 并重启。
7.2 自动切换模式(Controller / DLedger)
RocketMQ 5.x 引入 Controller 组件实现自动主从切换。Controller 基于 Raft 协议选举,可嵌入 NameServer 或独立部署。
NameServer 嵌入 Controller 配置(在 namesrv.conf 中):
properties
enableControllerInNamesrv = true
controllerDLegerGroup = group1
controllerDLegerPeers = n0-192.168.1.10:9877;n1-192.168.1.11:9878;n2-192.168.1.12:9879
controllerDLegerSelfId = n0
controllerStorePath = /home/admin/DledgerController
enableElectUncleanMaster = false # 不允许选举数据落后的节点为 Master
notifyBrokerRoleChanged = true
Broker 端开启 Controller 模式:
properties
enableControllerMode = true
controllerAddr = 192.168.1.10:9877;192.168.1.11:9878;192.168.1.12:9879
新手提醒:Controller 需要至少 3 个节点(遵循 Raft 多数派协议)。enableElectUncleanMaster=false 保证不会选举数据落后的节点,避免消息丢失。
八、监控搭建与 Dashboard
8.1 部署 RocketMQ Dashboard
Dashboard 是官方可视化管控台,提供 Broker 状态查看、Topic 管理、消息查询、消费堆积监控等功能。
bash
# 方式一:Docker 部署(推荐新手)
docker run -d --name rocketmq-dashboard \
-p 8080:8080 \
-e "JAVA_OPTS=-Drocketmq.namesrv.addr=192.168.1.10:9876;192.168.1.11:9876" \
apacherocketmq/rocketmq-dashboard:latest
访问 http://服务器IP:8080 即可使用。Dashboard 的核心功能面板包括:驾驶舱(集群总览)、Broker 管理、Topic 管理、消费者管理、消息查询等。
8.2 查看消息堆积
Dashboard 中进入 消费者(Consumer) 页面,可以看到每个消费组的堆积量(Diff Total)。堆积量为正数表示有未消费消息。
8.3 消息队列滞留时间
"滞留时间"本质上是 最早未消费消息的生产时间与当前时间的差值。RocketMQ 提供的相关监控指标为 instance_retention_period(实例消息保留时间),表示当前时间与节点保存的最早一条消息的时间差。
通过 Dashboard 排查:在 Consumer 详情中查看堆积量,再通过消息轨迹查询最早未消费消息的生产时间,两者相减即为滞留时长。
通过命令行排查:
bash
# 查看消费组堆积情况
sh bin/mqadmin consumerProgress -n 192.168.1.10:9876 -g your-consumer-group
输出中 Diff 列表示堆积量,结合业务可估算滞留时间。
8.4 消息堆积处理流程
- 确认堆积位置:查看客户端日志 ons.log,搜索 the cached message count exceeds the threshold,出现则说明堆积在客户端本地缓冲队列,否则在 Broker 端。
- 排查消费耗时:若消费耗时过长(如单条处理超过 1 秒),检查业务逻辑是否包含慢查询、同步 IO 等。
- 提升消费能力:增加消费者实例、调大消费线程数、优化业务逻辑。
- 紧急处理:如果堆积量极大,可临时新建 Topic + 新消费者组,用多线程快速消费转发后再处理。
九、生产环境可靠性汇总
维度 配置项 推荐值
刷盘策略 flushDiskType SYNC_FLUSH(核心业务)
主从复制 brokerRole SYNC_MASTER(核心业务)
发送方式 Producer 同步发送 + 重试
消费确认 Consumer 手动 ACK,异常返回 RECONSUME_LATER
幂等去重 消费端 Redis SETNX 或数据库唯一约束
主从切换 Controller enableControllerMode=true
监控 Dashboard 实时监控堆积量
十、新手常见问题速查
- Broker 启动报内存不足:修改 runbroker.sh 中的 JVM 参数为 -Xms512m -Xmx512m。
- Slave 无法连接 Master:检查 brokerName 是否一致、10912 端口是否开放。
- 消息发送超时:检查 NameServer 地址是否正确、防火墙是否开放 9876 端口。
- 顺序消费不生效:确认 consumeMode = ConsumeMode.ORDERLY 且发送时使用了 syncSendOrderly。
- 事务消息一直回查:检查 checkLocalTransaction 是否返回了正确的状态,避免返回 UNKNOWN。
- 堆积量持续增长:优先检查消费者线程是否被阻塞(如数据库慢查询、同步 HTTP 调用)。