RocketMQ 一主两从集群搭建与 SpringBoot 集成完整方案

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 消息堆积处理流程

  1. 确认堆积位置:查看客户端日志 ons.log,搜索 the cached message count exceeds the threshold,出现则说明堆积在客户端本地缓冲队列,否则在 Broker 端。
  2. 排查消费耗时:若消费耗时过长(如单条处理超过 1 秒),检查业务逻辑是否包含慢查询、同步 IO 等。
  3. 提升消费能力:增加消费者实例、调大消费线程数、优化业务逻辑。
  4. 紧急处理:如果堆积量极大,可临时新建 Topic + 新消费者组,用多线程快速消费转发后再处理。

九、生产环境可靠性汇总

维度 配置项 推荐值

刷盘策略 flushDiskType SYNC_FLUSH(核心业务)

主从复制 brokerRole SYNC_MASTER(核心业务)

发送方式 Producer 同步发送 + 重试

消费确认 Consumer 手动 ACK,异常返回 RECONSUME_LATER

幂等去重 消费端 Redis SETNX 或数据库唯一约束

主从切换 Controller enableControllerMode=true

监控 Dashboard 实时监控堆积量

十、新手常见问题速查

  1. Broker 启动报内存不足:修改 runbroker.sh 中的 JVM 参数为 -Xms512m -Xmx512m。
  2. Slave 无法连接 Master:检查 brokerName 是否一致、10912 端口是否开放。
  3. 消息发送超时:检查 NameServer 地址是否正确、防火墙是否开放 9876 端口。
  4. 顺序消费不生效:确认 consumeMode = ConsumeMode.ORDERLY 且发送时使用了 syncSendOrderly。
  5. 事务消息一直回查:检查 checkLocalTransaction 是否返回了正确的状态,避免返回 UNKNOWN。
  6. 堆积量持续增长:优先检查消费者线程是否被阻塞(如数据库慢查询、同步 HTTP 调用)。
相关推荐
青山木1 天前
RocketMQ 入门到原理(一):整体架构与消息的生命周期
java·分布式·后端·中间件·架构·rocketmq
凤山老林3 天前
RocketMQ 5.0 实战避坑与调优:事务、延迟、轨迹及高可用落地指南
spring boot·rocketmq
heimeiyingwang3 天前
【中台·技术篇】消息队列中台:Kafka/RocketMQ 统一管理与多租户隔离
kafka·rocketmq·中台
hey you~5 天前
云客服多渠道统一接入,消息队列技术实现方案
kafka·消息队列·rocketmq·系统集成·云客服·多渠道接入·接口对接
letisgo58 天前
JAVA 高级进阶10篇《消息队列实战:RocketMQ/Kafka选型与“不丢不重有序“三连解》
java·面试·kafka·消息队列·rocketmq
小楼昨夜又东风1269 天前
kafka、rocketmq、rabbitmq,有什么区别
kafka·rabbitmq·rocketmq
一本正经的不务正业11 天前
kafka与RocketMQ的不同
分布式·kafka·rocketmq
阿里云云原生12 天前
深入解析 RocketMQ LiteTopic:如何以百万级轻量主题实现用户级消息治理?
rocketmq
oliver_sys_log16 天前
RocketMQ 4.5.1 延迟消息"发送成功但消费不到"排查分析报告
后端·rocketmq