主题
RocketMQ 高可用演进:从主从手动切换到 DLedger 与 Controller 模式
问题
RocketMQ 主从架构下,Master 挂了会发生什么?为什么说 4.x 的自动切换是"假的"?DLedger 模式用 Raft 换自动 failover 到底值不值?5.x 的 Controller 模式又是怎么回事,跟 DLedger 比有什么不一样?面试官问「RocketMQ 高可用怎么落地」,你至少得说清楚三个阶段的演进逻辑。
阶段一:Master-Slave 手动切换时代
架构
RocketMQ 从诞生起就是主从架构。一个 Broker 组由一个 Master 和 0 到多个 Slave 组成:
Producer ──► Master (写/读) ──► Slave (只读)
▲
Consumer ─────────────────────────────┘- Master:处理生产者的写入请求,也处理消费者的拉取请求
- Slave:从 Master 同步数据,只处理消费者的拉取请求
- 同步方式:同步复制(
SYNC_MASTER)或异步复制(ASYNC_MASTER)
问题的核心:Master 挂了谁当家?
4.x 的 Master-Slave 架构有个致命短板:Master 宕机后,集群不会自动将 Slave 提升为 Master。
流程是这样的:
- Master 宕机 → 生产者无法写入,返回
REMOTING_EXCEPTION - 消费者能用 Slave 继续消费已有数据,但消费进度没法提交给 Master(因为 Master 挂了)
- 运维手动改配置、重启 Slave 为 Master,期间生产中断
- 如果是异步复制,Master 宕机时可能还有一批数据没同步到 Slave,已确认的消息也会丢
bash
# 手动切换的典型操作(4.x 时代)
# 1. 停掉 Slave 的 Broker 进程
# 2. 修改 broker.conf:brokerId=0(变成 Master)
# 3. 启动 Broker,检查路由是否更新到 NameServer
# 4. 重启所有 Producer/Consumer(或等它们自动重连)这个手动过程快的几分钟,慢的因为审批、定位、备机操作,随随便便半小时过去。线上核心链路扛不住这种窗口。
可用性指标
Master-Slave 架构的理论可用性取决于同步方式:
- 异步复制:Master 宕机可能丢消息,RPO > 0。RTO 取决于手动切换时间,分钟到小时级别。
- 同步复制:消息不丢(RPO = 0),但写入性能下降约 30%-50%,且 Master 宕机后仍然需要手动切换,RTO 没改善。
所以同步复制只是保了数据不丢,对高可用(自动恢复)没有帮助。
阶段二:DLedger 模式——引入 Raft 做自动选主
为什么是 Raft
RocketMQ 4.5 引入了 DLedger 模式,本质上是用 Raft 一致性协议来管理 CommitLog 的副本。每个 Broker 组变成 3 个节点(奇数个),通过 Raft 选举出 Leader(相当于 Master),Follower 自动同步数据。
DLedger Broker Group(3 节点)
┌──────────────────────────────────────┐
│ Node A (Leader) ← 读写入口 │
│ Node B (Follower) ─ 同步数据 │
│ Node C (Follower) ─ 同步数据 │
└──────────────────────────────────────┘工作流程
- 选主:节点启动时或 Leader 宕机后,通过 Raft 的 RequestVote 机制选主。需要多数派(≥2/3)节点在线才能选出 Leader。
- 写入:Producer 发给 Leader → Leader 写入本地 CommitLog → 并行发给 Follower → Follower 写入后回复 ACK → Leader 收到多数派 ACK 后返回成功。
- 读取:只有 Leader 能处理写入,Follower 只响应消费者的拉取(Raft 的线性一致性读,Leader 需要确认自己仍是 Leader)。
- 故障转移:Leader 宕机 → Follower 发现心跳超时(约 1-2 秒,可配置)→ 发起选举 → 选出新 Leader → NameServer 更新路由 → Producer/Consumer 重连。
java
// DLedger 配置示例(broker.conf)
brokerRole = ASYNC_MASTER // 实际由 DLedger 接管
dLegerGroup = broker-group-1
dLegerPeers = n0-127.0.0.1:60011;n1-127.0.0.1:60012;n2-127.0.0.1:60013
dLegerSelfId = n0代价
DLedger 不是没有成本的。因为每次写入都需要多数派确认,吞吐量相比异步复制 Master-Slave 下降约 20%-30%。延迟也会增加,因为多了网络往返和 Raft 日志落盘。
跟同步复制 Master-Slave 比,DLedger 的吞吐差距不大,甚至因为 Raft 的批量追加(batching)和流水线(pipelining)优化了网络开销,反而更高。
写入延迟对比(近似值,ms):
Async Master-Slave: 1-2ms
Sync Master-Slave: 5-8ms
DLedger: 4-7ms生产部署要点
- 节点数必须奇数:3 节点是最小配置,5 节点能容忍 2 个节点宕机
- 磁盘要快:DLedger 每个写入都需要落盘 fsync,SSD 是硬性要求
- 网络延迟敏感:同机房部署,跨机房 Raft 延迟会高到不可接受
- 消息不丢配置:
flushDiskType=SYNC_FLUSH+ DLedger 自动保证多数派落盘,不丢消息
阶段三:RocketMQ 5.x Controller 模式
为什么又搞一个
DLedger 解决了自动 failover,但有两个问题:
- 存储耦合:DLedger 替换了 CommitLog 的写入路径,意味着现有存储格式要做改动。迁移存量集群需要整个 Broker 组替换。
- 运维复杂度:DLedger 的节点管理和 Raft 配置对运维团队是个新东西,出问题排查路径不熟。
RocketMQ 5.x 的 Controller 模式把选主逻辑从 Broker 里抽出来,变成一个独立组件(Controller),Broker 层面还是原来的 Master-Slave 存储。
Controller 模式架构
Controller Cluster (3 节点,内嵌 Raft)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Controller 1 │ │ Controller 2 │ │ Controller 3 │
│ (Leader) │◄─│ (Follower) │──│ (Follower) │
└──────┬───────┘ └──────────────┘ └──────────────┘
│ 心跳 + 状态上报
▼
Broker Group A
┌──────────────┐ ┌──────────────┐
│ Master │ │ Slave │
│ (Active) │──│ (Standby) │
└──────────────┘ └──────────────┘工作流程
- 注册与心跳:每个 Broker 启动时向 Controller 注册,定时发送心跳(含自身状态:Master 还是 Slave)。
- 选主决策:Controller 集群通过内嵌的 Raft 协议保证一致性,选出一个 Leader Controller。当 Controller Leader 检测到 Broker Master 心跳超时,将 Slave 提升为 Master。
- 切换通知:Controller 通过
MasterChange事件通知 Slave 端切换角色,同时更新 NameServer 的路由信息。
bash
# 5.x Controller 部署方式:内嵌 Controller(和 Broker 同进程)或独立部署
# 推荐独立部署 3 个 Controller 节点
# controller 配置(controller/controller.conf)
controllerDLegerGroup = controller-group
controllerDLegerPeers = n0-127.0.0.1:9001;n1-127.0.0.1:9002;n2-127.0.0.1:9003
controllerDLegerSelfId = n0
# broker 配置(broker.conf)
enableControllerMode = true
controllerAddr = 127.0.0.1:9001;127.0.0.1:9002;127.0.0.1:9003DLedger vs Controller 模式
| 维度 | DLedger | Controller |
|---|---|---|
| 选主方式 | Broker 内嵌 Raft | 独立 Controller 内嵌 Raft |
| 存储兼容 | 替换 CommitLog 写入路径 | 兼容原 CommitLog 格式 |
| 迁移成本 | 整个 Broker 组替换 | 只需加 Controller 组件 |
| 运维复杂度 | 高(Raft 节点管理) | 低(Controller 独立部署) |
| 性能 | 略低于异步(-20%~30%) | 接近异步(写入路径不变) |
| 推荐场景 | 新集群、强一致要求 | 存量集群迁移、平滑升级 |
不丢消息的配置组合
不管是 DLedger 还是 Controller,要保证 Master 宕机不丢消息,需要:
bash
# broker.conf
flushDiskType = SYNC_FLUSH # 同步刷盘
brokerRole = SYNC_MASTER # 同步复制(Controller 模式下生效)
enableControllerMode = true # Controller 模式
# 或 DLedger 配置(互斥)
dLegerGroup = my-group跟 Kafka ISR 的横向对比
RocketMQ 高可用演化过程中,很多设计取舍跟 Kafka 的 ISR 机制有对照关系:
| 维度 | Kafka ISR | RocketMQ DLedger | RocketMQ Controller |
|---|---|---|---|
| 一致性协议 | 自定义 ISR(非 Raft) | Raft | 内嵌 Raft |
| 副本数 | 可配置(1+) | 建议 3 节点 | 2 + 独立 Controller |
| 选主速度 | 秒级(ISR 内选优先) | 1-2 秒 | 1-2 秒 |
| 脑裂防范 | epoch + fence | Raft 任期 | Controller Raft 任期 |
| 不丢数据前提 | min.insync.replicas + acks=all | 多数派 + SYNC_FLUSH | SYNC_MASTER + SYNC_FLUSH |
| 运维复杂度 | 低 | 中 | 中低 |
Kafka 的 ISR 机制更灵活,副本数伸缩更自由。RocketMQ 的 DLedger 在阿里系电商场景有大规模验证,生产稳定性有保障,但 Raft 的运维复杂度高于 ISR。
总结
- Master-Slave 手动切换(4.x 及以前)——RTO 分钟到小时,异步复制还可能丢消息。线上只能当"主备"用,不是真正的高可用。
- DLedger 模式(4.5+)——用 Raft 换自动选主,牺牲 20%-30% 吞吐换来秒级 RTO。适合新集群,不适用存量迁移。
- Controller 模式(5.x)——选主逻辑外置,兼容原存储。存量集群平滑升级的最佳选择,新项目也建议优先考虑。
选型建议:新集群用 DLedger(强一致、自动 failover),存量集群迁移用 Controller(低成本、兼容好)。不管哪种,同步刷盘 + 同步复制是线上不丢消息的底线。
参考
- RocketMQ 官方文档:DLedger 快速部署
- RocketMQ 5.x Controller 模式设计文档(RIP-44)
- Raft 论文:In Search of an Understandable Consensus Algorithm
- Kafka ISR 设计与对比