RocketMQ DLedger Leader 选举 流程详解 & 源码解析

原文地址: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 重新选举。
    • 客户端请求:处理日志复制
  • 状态转移:
    • 发现其他节点的心跳请求响应有更大的投票轮次:转换成 Follower。
2.4 Candidate
  • 行为:
    • 发起新的选举轮次(term += 1),给自己投票,然后向其他节点发起拉票请求。
      • 刚进入 Candidate 状态会发起新的投票轮次,先给自己投票。
      • 发起投票后,启动计时器,统计选举超时。当计时器到期仍未得到半数以上投票,开启新的投票轮次。
  • 发起请求:
    • 选举轮次启动时,给其他节点发拉票请求,获取其他节点的投票响应。
  • 处理请求:
    • 其他 Candidate 拉票请求:如果其他 Candidate 投票轮次大于当前轮次,则投票给它并更新当前轮次。否则无动作。
    • Leader 心跳请求:对比投票轮次,如果大于等于当前轮次,则说明已经有 Leader 选出,自己转换成 Follower。
  • 状态转移:
    • 选票超过半数:转换成 Leader。
    • 收到 Leader 心跳请求:此时其他节点已经是 Leader,转换成 Follower。
    • 选票未超过半数(Split vote):保持为 Candidate,在选举超时后开始新的选举轮次。

3. 详细设计

下图是 3 个 DLedger 节点组成的集群,其中中间的是主节点,左右两个从节点。

alt text

其中 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 协议集群节点的封装。

DLedgerProtocolHandlerDLedgerClientProtocolHandler 分别是 DLedger 服务端和客户端的协议处理器接口,定义了一个 DLedger 节点需要实现的协议处理方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值