原文地址:http://hscarb.github.io/rocketmq/20250204-rocketmq-dledger-leader-election.html
RocketMQ DLedger Leader 选举 流程详解 & 源码解析
1. 背景
在 RocketMQ 4.5.0 版本以前,只提供了主从同步功能,但是主节点宕机之后从节点只能读不能写,没有主从切换能力。RocketMQ 4.5.0 引入了 DLedger 来实现主从切换。
DLedger 是基于 Raft 算法的 WAL 实现,它是为消息场景量身定制的,提供了日志写入和读取的接口,且对顺序读出和随机读出做了优化,充分适应消息系统消峰填谷的需求。
在 RocketMQ 4.8.0 版本以前,DLedger 模式的性能比主备模式差 1 个数量级^1,所以建议用尽可能新的版本部署。
DLedger 的实现大体可以分为两个部分,Leader 选举和日志复制,本文基于 DLedger 0.2.7 版本源码详解 Leader 选举的设计和流程。
2. 概要设计
2.1 节点状态流转
DLedger 的主从同步主要是实现了 Raft 协议,我们先来看一下 Raft 协议中节点的 3 个角色之间的行为和转换。
stateDiagram-v2
[*] --> Follower
Follower --> Candidate: 距上次收到心跳时间\n超过选举超时
note right of Follower
收到 Leader 有效心跳
重置计时器
end note
Leader --> Follower: 发现更高任期
note right of Leader
定期发送心跳
来维持领导地位
end note
Candidate --> Leader: 获得多数投票
Candidate --> Follower: 发现更高任期\n或收到已有 Leader 心跳
note right of Candidate
超过选举超时未获得多数投票
发起新一轮选举
end note
Raft 选主的过程中有一些重要的请求类型:
- 心跳请求:Leader 节点定期发送心跳请求给其他节点,以维持领导地位。
- 投票请求:Candidate 节点在选举超时后,会发起投票请求,向其他节点请求投票。由于是向其他节点请求投票,所以我们后面把它称为拉票请求方便理解。
- 其它的客户端请求和日志追加请求不在 Leader 选举流程中,本文暂不展开。
Raft 选主流程中还有两个重要的 timeout:
- 心跳超时:Leader 节点发送心跳请求给其他节点的间隔时间,DLedger 里是 2s。
- 选举超时:Follower 在选举超时后,会成为 Candidate,启动选主流程。DLedger 里是 3 倍的心跳超时,即 6s。
下面我们看一下每种角色的节点的行为。
2.2 Follower
- 行为:
- 启动计时器,时间为选举超时(Election timeout)。
- 发起请求:无
- 处理请求:
- Candidate 拉票请求:如果没有投过票则返回投票,更新投票轮次(为请求的轮次),并记录投票给谁。
- Leader 心跳请求:收到后会返回响应并重新启动计时器。
- 状态转移:
- 计时器到期:转换成 Candidate。
2.3 Leader
- 行为:
- 处理所有客户端请求和日志复制
- 每过心跳超时(Heartbeat timeout),向所有其他节点发送心跳请求,维持统治。
- 发起请求:
- 向其他节点发送心跳请求,处理心跳响应。如果收到的响应的轮次大于当前轮次,则转换为 Follower。
- 处理请求:
- Candidate 拉票请求:
- 轮次大于当前,转换成 Follower。
- 轮次小于等于当前,无动作。
- Leader 心跳请求:
- 轮次小于当前,无动作。
- 轮次大于当前,转换成 Follower。
- 轮次等于当前,可能是由于发生网络分区导致出现多个 Leader,都降为 Follower 重新选举。
- 客户端请求:处理日志复制
- Candidate 拉票请求:
- 状态转移:
- 发现其他节点的心跳请求响应有更大的投票轮次:转换成 Follower。
2.4 Candidate
- 行为:
- 发起新的选举轮次(term += 1),给自己投票,然后向其他节点发起拉票请求。
- 刚进入 Candidate 状态会发起新的投票轮次,先给自己投票。
- 发起投票后,启动计时器,统计选举超时。当计时器到期仍未得到半数以上投票,开启新的投票轮次。
- 发起新的选举轮次(term += 1),给自己投票,然后向其他节点发起拉票请求。
- 发起请求:
- 选举轮次启动时,给其他节点发拉票请求,获取其他节点的投票响应。
- 处理请求:
- 其他 Candidate 拉票请求:如果其他 Candidate 投票轮次大于当前轮次,则投票给它并更新当前轮次。否则无动作。
- Leader 心跳请求:对比投票轮次,如果大于等于当前轮次,则说明已经有 Leader 选出,自己转换成 Follower。
- 状态转移:
- 选票超过半数:转换成 Leader。
- 收到 Leader 心跳请求:此时其他节点已经是 Leader,转换成 Follower。
- 选票未超过半数(Split vote):保持为 Candidate,在选举超时后开始新的选举轮次。
3. 详细设计
下图是 3 个 DLedger 节点组成的集群,其中中间的是主节点,左右两个从节点。
其中 DLedgerStore
是日志存储类,收到日志追加请求后把日志保存,然后推给 DLedgerEntryPusher
类。
DLedgerEntryPusher
是 DLedger 的日志追加实现类,它会把本地的数据推到从节点的 DLedgerEntryPusher
,在从节点上保存。
DLedgerLeaderElector
则是负责 DLedger 主从切换的,它向其他节点收发心跳和投票请求,来执行主从切换。
3.1 类设计
classDiagram
direction TB
class DLedgerClientProtocolHandler {
<<interface>>
+handleAppend(AppendEntryRequest): AppendEntryResponse
+handleGet(GetEntriesRequest): GetEntriesResponse
+handleMetadata(MetadataRequest): MetadataResponse
+handleLeadershipTransfer(LeadershipTransferRequest): LeadershipTransferResponse
}
class DLedgerProtocolHandler {
<<interface>>
+handleVote(VoteRequest): VoteResponse
+handleHeartBeat(HeartBeatRequest): HeartBeatResponse
+handlePull(PullEntriesRequest): PullEntriesResponse
+handlePush(PushEntryRequest): PushEntryResponse
}
class DLedgerClientProtocol {
<<interface>>
+metadata(MetadataRequest): MetadataResponse
+get(GetEntriesRequest): GetEntriesResponse
+leadershipTransfer(LeadershipTransferRequest): LeadershipTransferResponse
+append(AppendEntryRequest): AppendEntryResponse
}
class DLedgerProtocol {
<<interface>>
+vote(VoteRequest): VoteResponse
+heartbeat(HeartBeatRequest): HeartBeatResponse
+pull(PullEntriesRequest): PullEntriesResponse
+push(PushEntryRequest): PushEntryResponse
}
class DLedgerRpcService {
<<abstract>>
}
class DLedgerRpcNettyService {
}
class DLedgerServer {
-memberState: MemberState
-dLedgerStore: DLedgerStore
-dLedgerRpcService: DLedgerRpcService
-dLedgerEntryPusher: DLedgerEntryPusher
-dLedgerLeaderElector: DLedgerLeaderElector
-executorService: ScheduledExecutorService
-fsmCaller: Optional~StateMachineCaller~
+registerStateMachine(StateMachine)
+handleHeartBeat(HeartBeatRequest): HeartBeatResponse
+handleVote(VoteRequest): VoteResponse
+handleAppend(AppendEntryRequest): AppendEntryResponse
+handleGet(GetEntriesRequest): GetEntriesResponse
+handleMetadata(MetadataRequest): MetadataResponse
+handlePull(PullEntriesRequest): PullEntriesResponse
+handlePush(PushEntriesRequest): PushEntriesResponse
+handleLeadershipTransfer(LeadershipTransferRequest): LeadershipTransferResponse
}
DLedgerProtocolHandler --|> DLedgerClientProtocolHandler : implements
DLedgerProtocol --|> DLedgerClientProtocol : implements
DLedgerRpcService ..|> DLedgerProtocolHandler : implements
DLedgerRpcService ..|> DLedgerProtocol : implements
DLedgerRpcNettyService --|> DLedgerRpcService
DLedgerServer ..|> DLedgerProtocolHandler
DLedgerServer *-- DLedgerRpcNettyService
上图为 DLedger 主要类的类图。
其中 DLedgerServer
表示一个 DLedger 节点,它是 Raft 协议集群节点的封装。
DLedgerProtocolHandler
和 DLedgerClientProtocolHandler
分别是 DLedger 服务端和客户端的协议处理器接口,定义了一个 DLedger 节点需要实现的协议处理方法。