在分布式系统中,多个节点之间如何就某个值达成一致,是计算机科学中最核心的挑战之一。无论是分布式数据库的选主、分布式锁的实现,还是配置管理系统的数据同步,都需要一个可靠的共识算法作为底层支撑。
Raft 算法由 Diego Ongaro 和 John Ousterhout 在 2014 年提出,其核心设计理念就是 可理解性(Understandability)——相比晦涩的 Paxos,Raft 将共识问题分解为三个相对独立的子问题,使得工程师能够更直观地理解和实现。
如今,Raft 已成为工业界最广泛采用的共识算法之一,被 etcd、Consul、TiKV、CockroachDB 等众多关键基础设施所采用。
二、Raft 的核心概念
2.1 节点状态机
Raft 集群中的每个节点(Peer)在任何时刻都处于三种状态之一:
- Leader(领导者):处理所有客户端请求,负责日志复制。一个任期内有且仅有一个 Leader。
- Follower(跟随者):被动接收 Leader 的请求,不主动发起通信。
- Candidate(候选者):在选举过程中临时存在的状态,用于竞选 Leader。
2.2 任期(Term)机制
Raft 将时间划分为任意长度的任期,每个任期以一个单调递增的数字标识。Term 在 Raft 中起到了逻辑时钟的作用:
- 每个节点持久化存储当前的 Term 号,通信时携带 Term。
- 节点发现自己的 Term 落后时,立即更新并退化为 Follower。
- Candidate 或 Leader 发现自己的 Term 落后(收到更高 Term 的请求),也立即退化为 Follower。
- 任何请求如果来自小于当前 Term 的请求,一律拒绝。
2.3 远程过程调用(RPC)
Raft 仅使用两种 RPC 进行节点间通信:
- RequestVote:Candidate 在选举期间发起,用于收集选票。
- AppendEntries:Leader 发起,用于日志复制,同时充当心跳机制(空日志条目即为心跳)。
三、领导者选举(Leader Election)
Leader 通过选举产生。当 Follower 在 选举超时(Election Timeout)内没有收到 Leader 的心跳时,就会触发选举:
- Follower 自增 Term,切换到 Candidate 状态,并投票给自己。
- 并行向其他节点发送 RequestVote RPC。
- 候选者等待以下三种结果之一:
- 赢得选举:获得超过半数节点的选票,成为新 Leader。赢得选举后立即向所有节点发送心跳,阻止新的选举。
- 其他节点赢得选举:收到 Term 不比自己小的 AppendEntries,说明已有 Leader 诞生,退化为 Follower。
- 选举超时:没有任何节点获得多数票(平票情况),Term 自增,重新发起选举。
3.1 随机化超时机制
为了避免多个 Follower 同时成为 Candidate 导致平票(Split Vote),Raft 使用随机化选举超时时间。通常每个节点的超时时间在 150ms~300ms 之间随机选择,这使得在大多数情况下,只有一个节点会率先超时并赢得选举。
论文中推荐的实践值是:心跳间隔 50ms,选举超时 150~300ms。即 Leader 大约每 50ms 发送一次心跳,如果约 3~6 个心跳周期内未收到心跳,Follower 就会触发选举。
四、日志复制(Log Replication)
Leader 当选后,开始接受客户端请求。每个请求被封装为一个 日志条目(Log Entry),其生命周期如下:
- Leader 将日志追加到本地。
- Leader 并行向所有 Follower 发送 AppendEntries RPC。
- 当超过半数的 Follower 确认追加成功后,Leader 将该日志应用到状态机(Apply),并返回结果给客户端。
- 后续的 AppendEntries 会携带最后已提交(Committed)的日志索引,通知 Follower 更新提交状态。
4.1 日志匹配特性
Raft 保证以下两个日志匹配特性,是正确性的基石:
- 如果两个日志条目具有相同的 Term 和 Index,那么它们存储相同的命令。
- 如果两个日志条目具有相同的 Term 和 Index,那么它们之前的所有日志条目都相同。
这些特性由 AppendEntries 的一致性检查保证:Leader 在发送 AppendEntries 时,会携带前一个日志的 Term 和 Index(prev_log_index 和 prev_log_term),如果 Follower 在该位置找不到匹配的日志,就会拒绝追加。
4.2 冲突解决
当 Follower 的日志与 Leader 不一致时(例如 Leader 崩溃导致日志缺失),Leader 不会尝试"修复"Follower 已有的日志,而是通过覆盖的方式解决冲突:
- Leader 维护每个 Follower 的
nextIndex,表示下一个要发送给该 Follower 的日志索引。 - 当 AppendEntries 因一致性检查失败时,Leader 递减
nextIndex并重试。 - 当最终找到一个匹配位置后,Leader 会用自身日志覆盖 Follower 该位置之后的所有日志。
五、安全性约束(Safety)
Raft 在选举和日志复制中增加了几条关键的安全性约束,保证状态机的强一致性:
5.1 选举限制
Leader 必须拥有所有已提交的日志条目。这意味着 Candidate 的日志必须"至少和其他节点一样新"。具体来说,在 RequestVote RPC 中实现如下检查:
- 比较 Candidate 和投票者最后一条日志的 Term,Term 更大者更新。
- 如果 Term 相同,则比较日志长度,长度更大者更新。
- 只有当 Candidate 的日志至少和投票者一样新时,投票者才会投出赞成票。
5.2 当前任期日志的提交限制
Raft 不会通过计算副本数来提交之前任期的日志条目。只有 Leader 当前任期的日志条目,才会通过计算副本数直接提交。这一规则避免了"已提交日志被覆盖"的悖论。
例如,如果 Leader 在 Term 4 追加了一条日志,但在提交前崩溃,新当选的 Term 5 Leader 可能也有自己的新日志。此时 Term 4 的日志必须等到 Leader 在当前 Term 提交一条新日志时,才能间接地被提交。
六、集群成员变更(Cluster Membership Changes)
在实际运维中,集群的节点会发生变化(扩缩容、故障替换等)。Raft 提出了联合共识(Joint Consensus)机制来处理这一复杂问题:
- Leader 收到从旧配置 C-old 转换到新配置 C-new 的请求。
- 先转换到联合配置 C-old,new(同时包含旧新两组成员),此期间所有决策需获得两组各自多数同意。
- 在联合配置稳定后,切换到 C-new,此时只需 C-new 的多数同意。
这种方式保证了在转换过程中不会出现两个不相交的多数派同时存在(脑裂),每个决策都能获得真正的多数批准。
实际工程中,如 etcd 采用了更简化的一次变更一个节点(Step-Down)策略,在大多数场景下避免了联合共识的复杂性。
七、工程实践要点
7.1 快照机制(Snapshotting)
日志不能无限增长,否则重启恢复和滞后节点追赶(Catch-up)的时间会过长。Raft 通过快照机制将日志压缩:
- 当日志超过一定大小时,Leader 生成快照,只保留最后一条已应用日志的 Term 和 Index。
- InstallSnapshot RPC:Leader 向严重滞后的 Follower 发送整个快照文件,使其快速跟上。
7.2 线性一致性读
简单地从 Leader 读取状态机数据,可能读到旧数据(如果 Leader 刚当选,其 Commit Index 尚未更新)。Raft 提供两种方案保证线性一致性读:
- ReadIndex:Leader 在读请求前记录当前 Commit Index,并发送一轮心跳确认自己仍是多数派 Leader,等到状态机应用到该 Index 后再读取。
- LeaseRead:Leader 在心跳成功后的租期内(Election Timeout 内),可以直接读取而不需额外确认,性能更高但依赖时钟。
7.3 Pre-Vote 优化
网络隔离的节点(如 IDC 故障分区)会持续发起选举,Term 急剧增长,当网络恢复后会用高 Term 扰乱稳定 Leader。Rote-Vote 优化要求 Candidate 在正式增加 Term 前先在 Pre-Vote 阶段尝试收集选票,只有获得多数同意才发起正式选举。
7.4 批量与流水线
为了提高吞吐量,工程实现中通常采用两种批量/流水线策略:
- 日志批量(Batch):Leader 不等每个日志分别发送,而是积累一定数量后一次性发送 AppendEntries。
- 流水线(Pipeline):Leader 不为每个 Follower 等待上一个 AppendEntries 的响应才发下一个,而是持续发送新的日志,类似 TCP 滑动窗口。
八、Raft 与 Paxos 对比
| 维度 | Raft | Paxos |
|---|---|---|
| 设计理念 | 可理解性优先,分解为独立子问题 | 理论完备性优先,Single-Diff 抽象 |
| 领导权 | Leader 驱动,简化冲突处理 | 无固定 Leader,Multi-Paxos 退化 |
| 日志连续性 | 强制连续,简化一致性检查 | 允许空洞,增加了实现复杂度 |
| 学习曲线 | 中等,有很好的可视化资源 | 较陡,Lamport 论文较难理解 |
| 工程实现 | 很多开源实现,文档丰富 | 标准实现较少,多为定制 |
| 一致性强度 | 与 Paxos 等价(Linearizability) | 等价 |
九、总结
Raft 通过将共识问题分解为领导者选举、日志复制三个相对独立的子问题,并引入了 Term 状态机、日志匹配、随机化超时等机制,在保证强一致性的同时大幅降低了理解和实现难度。
理解 Raft 的关键不在于记忆每个状态转换的细节,而在于体会其核心思想:通过 Leader 简化决策流程,通过多数派(Quorum)保证容错,通过日志顺序保证状态机的一致性。这一思想不仅适用于 Raft,也是理解所有分布式系统设计的通用框架。
在实际工程应用中,除了掌握 Raft 的基本原理,还需要重点关注快照压缩、线性一致性读、成员变更、Pre-Vote 等工程细节,这些才是决定一个 Raft 生产级实现可靠性的关键所在。
参考资料
- Ongaro D, Ousterhout J. In Search of an Understandable Consensus Algorithm. USENIX ATC 2014.
- Raft 可视化演示: https://raft.github.io
- etcd Raft 实现: https://github.com/etcd-io/raft

发表评论 取消回复