Raft 共识算法详解
Raft 是 Diego Ongaro 和 John Ousterhout 于 2013 年提出的共识算法,核心设计目标是功能与 Paxos 等价但更易于理解和工程实现。Raft 将共识问题分解为三个相对独立的子问题:领导者选举(Leader Election)、日志复制(Log Replication)、安全性(Safety)。
1.1 基础概念
- 节点状态:每个节点在任何时刻处于三种状态之一——Leader(最多一个,处理所有客户端请求)、Follower(被动响应 Leader 和 Candidate 的 RPC)、Candidate(竞选 Leader 的中间状态)
- Term(任期):逻辑时钟概念,用连续递增的整数表示。每个 Term 最多产生一个 Leader,如果选举失败(选票分散),该 Term 以无 Leader 结束,进入下一个 Term
- 心跳机制:Leader 周期性向所有 Follower 发送 AppendEntries RPC(即使没有新的日志要复制),Follower 收到心跳后重置选举超时计时器——心跳压制了 Follower 发起选举的冲动
7.2.2 Leader Election(领导者选举)
- 所有节点以 Follower 身份启动
- Follower 在 election timeout(随机 150–300ms)内未收到 Leader 的心跳 → 自身变为 Candidate,将当前 Term +1
- Candidate 先给自己投一票,然后并行向所有其他节点发送 RequestVote RPC
- 三种可能的结果:
- 赢得选举:在一个 Term 内获得超过半数选票 → 成为 Leader,立即向所有节点发送心跳(空的 AppendEntries)建立权威,阻止新的选举
- 其他节点成为 Leader:收到来自新 Leader 的心跳,且心跳中的 Term ≥ 自身 Term → 承认新 Leader,退回 Follower 状态
- 选票分散(Split Vote):无人获得多数票 → 超时后 Term +1,重新发起选举。随机超时机制(每个 Candidate 的选举超时独立随机)极大降低了反复分散的概率
7.2.3 Log Replication(日志复制)
- Leader 接收客户端写请求,将命令封装为日志条目追加到本地日志中
- Leader 并行向所有 Follower 发送 AppendEntries RPC,携带需要复制的日志条目
- 当某条日志条目被超过半数的节点安全写入后,Leader 将该条目标记为 committed,然后应用到状态机(执行命令),并将结果返回客户端
- Leader 在后续的 AppendEntries 中携带 leaderCommit 索引,通知各 Follower 哪些条目已被提交——Follower 据此将对应条目应用到自己的状态机
Raft 的日志复制保证了 Log Matching Property(日志匹配特性):如果不同日志中两条条目拥有相同的 Index 和 Term,则它们存储的命令相同、且该 Index 之前的所有条目也完全一致。这是 Raft 安全性保证的基石。
7.2.4 安全性保证
- Election Restriction:Candidate 的日志必须至少和大多数节点一样”新”(比较最后一个日志条目的 Term 和 Index),否则不会被选为 Leader。这保证了已提交的日志不会在新 Leader 上任后被覆盖
- Commitment Rule:Leader 只能通过计数副本数来提交当前 Term 的日志条目,历史 Term 的条目通过”连带提交”(Leader 提交了一条当前 Term 的日志后,所有之前的日志也随之提交)
- Configuration Change:使用 Joint Consensus(联合共识)在两阶段中安全地变更集群成员
实现案例:etcd、Consul、TiKV、CockroachDB
推荐阅读:Raft 论文(《In Search of an Understandable Consensus Algorithm》)、Raft 官网的可视化动画、MIT 6.824 课程的 Raft 实验