RocketMQ 的 DLedger 到底做了什么?从 Raft 选主说起,对比 Kafka KRaft

0 阅读8分钟

上篇留下的问题

上篇聊到 RocketMQ 传统主从模式下,Master 挂了需要人工介入切换。结尾提到 4.5 版本引入的 DLedger 模式解决了这个问题。这篇就来拆开 DLedger,看看它到底做了什么,以及它和 Kafka KRaft 在选主机制上的本质差异在哪。

DLedger 是什么

DLedger 本质上是一个基于 Raft 协议的 CommitLog 存储库,由 OpenMessaging 发布,是一个可以直接引用的 Java 类库。

RocketMQ 集成 DLedger 的方式很直接:用 DLedger CommitLog 替换掉原来的 CommitLog,让 CommitLog 本身具备选举和复制的能力。然后通过“角色透传”把 Raft 的角色映射到 Broker 角色上——Raft 的 Leader 对应原来的 Master,Follower 和 Candidate 对应 Slave。Broker 与 NameServer、客户端的交互仍然以 Master/Slave 的形式进行,但底层的主从切换能力由 Raft 提供。

这个设计的好处是:Broker 的上层逻辑几乎不用改,Raft 的能力通过 DLedger 这个中间层注入进去。

DLedger 的选主流程

DLedger 的选主过程是一个状态机的流转。集群启动时,每个节点的等待超时时间是随机的,第一个结束倒计时的节点会主动向其他节点发起投票请求,在收到半数以上的投票后晋升为 Leader。

具体的状态流转:

  1. 所有节点初始状态为 Follower,各自维护一个随机的选举超时时间。
  2. 最先超时的节点切换为 Candidate,任期号(Term)加一,先投自己一票,然后向其他节点发送投票请求。
  3. 其他节点收到投票请求后,根据“当前任期是否更新”“日志是否至少和自己一样新”等条件决定是否投票。一个任期内的投票原则是先到先得,每个节点在一个任期内只能投一票。
  4. Candidate 获得超过半数的投票后成为 Leader,立即向其他节点发送心跳,声明自己的 Leader 地位。
  5. 如果选举超时(没有获得足够票数),Candidate 重新发起选举,随机等待一段时间后重试,避免多个 Candidate 反复冲突。

选举过程中有两个关键概念:Term(任期) 和 Quorum(多数派) 。Term 是一个单调递增的整数,每次选举都会递增,用来区分不同的选举轮次。Quorum 要求超过半数的节点同意,比如 3 个节点需要 2 个同意,5 个节点需要 3 个同意。这保证了一个 Term 内最多只有一个 Leader 当选,避免脑裂。

上篇留下的问题

上篇聊到 RocketMQ 传统主从模式下,Master 挂了需要人工介入切换。结尾提到 4.5 版本引入的 DLedger 模式解决了这个问题。这篇就来拆开 DLedger,看看它到底做了什么,以及它和 Kafka KRaft 在选主机制上的本质差异在哪。

DLedger 是什么

DLedger 本质上是一个基于 Raft 协议的 CommitLog 存储库,由 OpenMessaging 发布,是一个可以直接引用的 Java 类库。

RocketMQ 集成 DLedger 的方式很直接:用 DLedger CommitLog 替换掉原来的 CommitLog,让 CommitLog 本身具备选举和复制的能力。然后通过“角色透传”把 Raft 的角色映射到 Broker 角色上——Raft 的 Leader 对应原来的 Master,Follower 和 Candidate 对应 Slave。Broker 与 NameServer、客户端的交互仍然以 Master/Slave 的形式进行,但底层的主从切换能力由 Raft 提供。

这个设计的好处是:Broker 的上层逻辑几乎不用改,Raft 的能力通过 DLedger 这个中间层注入进去。

DLedger 的选主流程

DLedger 的选主过程是一个状态机的流转。集群启动时,每个节点的等待超时时间是随机的,第一个结束倒计时的节点会主动向其他节点发起投票请求,在收到半数以上的投票后晋升为 Leader。

具体的状态流转:

  1. 所有节点初始状态为 Follower,各自维护一个随机的选举超时时间。
  2. 最先超时的节点切换为 Candidate,任期号(Term)加一,先投自己一票,然后向其他节点发送投票请求。
  3. 其他节点收到投票请求后,根据“当前任期是否更新”“日志是否至少和自己一样新”等条件决定是否投票。一个任期内的投票原则是先到先得,每个节点在一个任期内只能投一票。
  4. Candidate 获得超过半数的投票后成为 Leader,立即向其他节点发送心跳,声明自己的 Leader 地位。
  5. 如果选举超时(没有获得足够票数),Candidate 重新发起选举,随机等待一段时间后重试,避免多个 Candidate 反复冲突。

选举过程中有两个关键概念:Term(任期) 和 Quorum(多数派) 。Term 是一个单调递增的整数,每次选举都会递增,用来区分不同的选举轮次。Quorum 要求超过半数的节点同意,比如 3 个节点需要 2 个同意,5 个节点需要 3 个同意。这保证了一个 Term 内最多只有一个 Leader 当选,避免脑裂。

deepseek_mermaid_20261005_41fc07.png

DLedger 的日志复制

选主完成后,Leader 开始接收客户端的写入请求。写流程分为两步:追加(Append)和提交(Commit) 。

客户端发送一条消息到 DLedger 集群,Leader 先将消息写入本地存储,此时这条日志是“未提交”状态。然后 Leader 将日志广播给所有 Follower,Follower 收到后存储并返回 ACK。

Leader 会持续收集 Follower 的 ACK。当超过半数的 Follower 都确认写入后,Leader 更新已提交指针(committedIndex),此时这条日志才被认为正式提交,客户端才能通过 Leader 访问这条消息。

这里有一个关键的设计细节:未提交的日志不能被客户端访问。 即使 Leader 本地已经写入了,只要还没收到多数派的 ACK,这条日志对外就是不可见的。这个设计保证了即使 Leader 在提交前宕机,新选出的 Leader 也一定能看到这条日志(因为多数派已经确认了),从而保证数据一致性。

落后的 Follower 如何追赶?DLedger 的机制是:Leader 会按照日志序号源源不断地向 Follower 转发日志。Follower 接收后放入待写队列,然后从自己当前的最大日志序号的下一条开始,依次尝试追加。如果序号连续,就写入;如果中间有缺失,就等待 Leader 重新发送缺失的部分。

跟 Kafka KRaft 的核心差异

Kafka 的 KRaft 模式在选主机制上和 DLedger 有一个本质差异:KRaft 的选举对象是 Controller,而 DLedger 的选举对象是 Broker 本身。

在 KRaft 模式下,Controller Quorum 是一个由 3 到 5 个节点组成的 Raft 集群,通过多数派投票选举出一个 Active Controller。这个 Active Controller 负责管理集群元数据,包括分区分配、Leader 选举等。而 Broker 的分区 Leader 选举是由 Controller 代理完成的,优先从 ISR(In-Sync Replicas)列表中选择存活的副本。

而在 DLedger 模式下,Raft 的选举和日志复制直接作用在 Broker 的数据层——CommitLog 上。Leader 就是原来的 Master,Follower 就是 Slave,选主和消息写入是同一层的事情,不需要额外的 Controller 来代理。

这个差异带来两个工程后果:

第一,DLedger 的写入路径更长。 因为 Raft 的多数派 ACK 机制直接作用在消息写入上,每条消息都必须等到多数派确认后才能返回。三副本需要两个节点确认,五副本需要三个节点确认。这导致写入延迟比异步复制模式更高。

第二,KRaft 的元数据管理和数据复制是解耦的。 Controller Quorum 只负责元数据一致性,Broker 之间的数据复制不受 Raft 多数派机制的直接约束。这意味着 Kafka 可以在元数据层面保证强一致,同时在数据层面保持较高的吞吐量。

简单说,DLedger 是“把 Raft 塞进存储层”,KRaft 是“用 Raft 管元数据层”。两者的目标都是自动选主,但作用的位置不同。

RocketMQ 5.0 的 Controller 模式

DLedger 模式虽然解决了自动选主,但它有两个明显的局限:一是每个 Broker 组至少需要三副本,成本较高;二是由于存储复制链路用的是 OpenMessaging DLedger 库,RocketMQ 原生的存储能力(如 TransientStorePool 和零拷贝)无法直接使用。

RocketMQ 5.0 之后引入了 Controller 模式,把 DLedger(Raft)能力从复制链路上移到 Controller,将选主切换能力上移,单独作为一个选主组件。Controller 可以内嵌在 NameServer 中,也可以独立部署。

这个演进方向的思路是:把“选主”和“数据复制”解耦。 Controller 负责选主和元数据管理,Broker 保持轻量存储角色。这样 Brokers 之间的数据复制不再需要 Raft 的多数派 ACK,而是走主备确认复制算法,性能和灵活性都更好。

Controller 模式下,Raft 算法用于选举 Controller 中的 Active Controller,由它负责处理数据面的选举和同步任务,其余 Controller 只负责同步 Active Controller 的处理结果。

这篇文章的局限

这篇文章没有深入讲 Raft 算法本身的细节——Term 的推进规则、选举安全性的具体约束、日志匹配的检查逻辑,这些内容展开需要很长的篇幅。另外,DLedger 和 KRaft 在细节实现上还有很多差异(比如 KRaft 用的是拉模式复制,DLedger 用的是推模式),这些也留到下一篇。

结尾: 回到 DLedger 和 KRaft,说明这两个系统选择 Raft 不是偶然,而是工程化权衡的结果。