在分布式存储与计算领域,**分布式共识(Distributed Consensus)**是解决数据多副本强一致性(Linearizability)与高可用容灾的核心基石。传统的 Paxos 算法以晦涩深奥著称,其学术理论与工程落地之间存在巨大鸿沟。为了解决 Paxos “难以理解、难以工业化实现” 的痛点,斯坦福大学的 Diego Ongaro 与 John Ousterhout 于 2014 年发表了经典论文《In Search of an Understandable Consensus Algorithm》,提出了 Raft 算法

如今,Raft 已成为工业界事实上的共识标准,驱动着包括 etcd(Kubernetes 元数据引擎)、TiKV(TiDB 分布式存储引擎)、CockroachDB、HashiCorp Consul 以及 Kafka KRaft 在内的诸多重量级基础设施。

本文将深入拆解 Raft 算法的设计哲学与核心运行机制,从节点角色转换、Leader 选举、日志复制流水线、核心安全性不变量(Safety Invariants),到日志压缩快照与成员变更,结合精准规范的 D2 架构图进行全方位剖析。


Raft 的核心设计哲学:问题分解与状态简化

相比 Paxos 将共识过程交织在复杂的两阶段提交协商中,Raft 提出了**强领导者(Strong Leader)**模型,将复杂的分布式共识问题严谨分解为三个相互独立的子问题:

  1. Leader 选举 (Leader Election): 当现有 Leader 宕机或集群初始化时,如何快速、确定性地选举出唯一的新 Leader。
  2. 日志复制 (Log Replication): Leader 接收客户端写指令并组织为日志条目(Log Entries),强行将其单向同步到其他节点并协调一致性。
  3. 安全性保证 (Safety): 设立严格的不变量(如选举受限机制),确保任何被提交的日志条目绝不会被后续的新 Leader 覆盖或回滚。
D2 Diagram
qtopie.github.io

节点角色与逻辑时钟(Term)

在 Raft 集群中,任意时刻每个节点必然处于以下三种角色之一:

逻辑时钟:任期 (Term)

分布式系统缺少全局物理时钟,Raft 采用单调递增的整数 Term(任期) 作为逻辑时钟(Logical Clock)

D2 Diagram
qtopie.github.io

核心机制一:Leader 选举与心跳机制

选举触发与投票流程

  1. 选举超时触发: 每个 Follower 维持一个计时器(Election Timeout,通常设置为 150ms ~ 300ms)。如果在此时间内未收到来自当前 Leader 的心跳(空的 AppendEntries RPC)或 Candidate 的拉票请求,Follower 认为 Leader 已故障,启动选举。
  2. 转换为 Candidate:
    • 本地任期编号加一:currentTerm = currentTerm + 1
    • 角色转为 Candidate,将当前 Term 内唯一的选票投给自己;
    • 重置选举计时器,向集群内所有其他节点并发广播 RequestVote RPC。
  3. 竞选结果的可能走向:
    • 赢得选举: Candidate 获得了集群中**过半数(Quorum,即 $\lfloor N/2 \rfloor + 1$)**节点的赞同票,正式晋升为 Leader,立即向全体 Follower 广播空 AppendEntries 周期性心跳,宣示主权并阻止后续选举。
    • 输掉选举: 在等待投票期间,收到了自称 Leader 发来的心跳且其 Term 不小于自身 Term,则承认其合法性,回退为 Follower。
    • 选举未决(Split Vote): 如果多个 Follower 同时超时并发起拉票,选票可能被均分(如 4 节点集群各自获得 2 票),无人能获得过半数。此时超时计时器到期后自动递增 Term 开启下一轮竞选。

随机超时(Randomized Election Timeout)防平票

为了根除分布式选举中多节点同时超时引发的死循环(反复平票 Split Vote),Raft 引入了简洁而精妙的机制:随机化超时时间。每个节点的选举超时时间从一个离散区间随机选取(例如 150ms ~ 300ms)。由于节点间的超时时间被打散,通常只有一个节点会率先超时并完成向大多数节点的拉票与心跳确权,从而使 Split Vote 发生的概率降至极低。


核心机制二:日志复制与状态机应用

Raft 采用**复制状态机(Replicated State Machine, RSM)**架构:相同的状态机只要按照完全一致的顺序执行完全相同的指令序列,就能产生完全一致的最终状态。

日志条目结构 (Log Entry)

每条日志条目包含三个核心要素:

复制执行完整流水线

D2 Diagram
qtopie.github.io
D2 Diagram
qtopie.github.io

日志一致性检查与纠错机制

为了保证全局日志序列的绝对一致,Follower 在接收到 Leader 的 AppendEntries 时会执行前置归纳校验:

  1. 递归一致性检测: Leader 在发送的 RPC 中携带 prevLogIndexprevLogTerm
  2. Follower 校验: Follower 检查本地在 prevLogIndex 位置上的日志条目的 Term 是否等于 prevLogTerm
    • 若匹配: 说明两节点在 prevLogIndex 及之前的所有日志完全一致,Follower 放心将后续的新日志追加到本地,覆盖任何可能存在的冲突旧日志。
    • 若不匹配: Follower 坚决拒绝该请求并返回失败。
  3. Leader 回溯同步: 收到拒绝后,Leader 将对应 Follower 的 nextIndex 递减并重新发送 AppendEntries,直到找到两者日志完全吻合的一致点,随后 Leader 的日志会强行覆盖 Follower 在一致点之后的全部冲突日志。

安全性不变量(Safety Invariants)

Raft 算法最硬核的理论精髓在于其对 5 大核心安全性不变量(Safety Properties)的数学保证:

不变量名称英文定义核心含义与保障机制
选举安全性Election Safety每个任期(Term)内最多只能有 1 个 Leader 被选出。
Leader 只能追加Leader Append-OnlyLeader 从不修改或截断自己的日志,只允许顺序向后追加新日志。
日志匹配特性Log Matching Property如果两节点日志在某个 Index 处具有相同的 Term,则该 Index 及之前的所有日志条目完全一致。
Leader 完整性Leader Completeness一旦一条日志在某个任期被提交(Committed),它必然存在于之后所有更高任期的 Leader 日志中。
状态机安全State Machine Safety如果一个节点已将某 Index 处的日志应用到状态机,其他节点在此 Index 处绝不会应用不同的指令。

核心限制 1:选举限制(Election Restriction)

如何确保拥有最全日志的节点才能当选 Leader?Raft 在投票阶段设立了日志完整性检查

核心限制 2:Leader 绝不能直接提交之前任期的旧日志

这是 Raft 论文中最经典、最微妙的限制(对应论文 Figure 8):


生产落地关键:日志压缩与快照(Snapshotting)

在长期运行的生产系统中,WAL 日志文件如果无限膨胀,不仅会耗尽磁盘空间,还会导致节点重启时重放日志恢复状态机极其缓慢。

Raft 采用 Copy-on-Write 内存快照(Snapshotting) 技术解决这一问题:

  1. 局部截断: 系统定期对当前状态机生成全量持久化镜像(Snapshot),保存当前的 lastIncludedIndexlastIncludedTerm
  2. 丢弃日志: 节点将本地所有 index <= lastIncludedIndex 的 WAL 日志安全删除。
  3. 落后 Follower 补齐(InstallSnapshot RPC): 如果某个 Follower 掉线过久,其所需的日志已经在 Leader 端被快照截断清理,常规的 AppendEntries 无法再向其提供增量日志。此时 Leader 会直接通过 InstallSnapshot RPC 将全量快照镜像发送给 Follower,Follower 直接加载快照并重置本地状态机基线。
D2 Diagram
qtopie.github.io

集群成员变更(Cluster Membership Changes)

集群在运行过程中经常需要扩容或缩容节点。直接从旧配置 $C_{old}$ 一步切换到新配置 $C_{new}$ 是极其危险的,因为不同节点的切换存在时间差,很容易在重叠期内由于法定多数(Quorum)计算不一致而同时选出两个 Leader(脑裂 Split-Brain)。

Raft 提出了两种安全的成员变更方案:

  1. 联合共识(Joint Consensus):
    • 进入过渡配置 $C_{old,new}$,在此阶段任何配置决策(日志提交与选举)必须同时获得 $C_{old}$ 的多数派与 $C_{new}$ 的多数派双方确认
    • 等到 $C_{old,new}$ 成功提交后,Leader 再提交 $C_{new}$ 彻底完成过渡。
  2. 单节点步进变更(Single-Server Changes):
    • 工业界(如 etcd)更青睐的简化实现:每次只允许增加或删除一个节点($N \rightarrow N+1$ 或 $N \rightarrow N-1$)。数学上可以证明,单节点变更不可能在重叠期形成两个独立的多数派,因而避免了复杂的双重 Quorum 逻辑。

工业级优化与工程实践

在真实的生产级共识引擎(如 etcd 的 etcd/raft 或 SOFA-Jraft)中,原版 Raft 论文的朴素实现通常会进行以下关键性能与稳定性优化:


核心算法对比:Paxos 与 Raft 的同构与异构

理解了 Raft 之后,完全不需要将 Paxos 当作另一个孤立神秘的算法去从头啃起。学术界已经证明:Raft 在共识容错能力和理论本质上与 Multi-Paxos 是等价同构的,两者的核心差异在于抽象建模的方式与对工程复杂度的控制

核心机制对比总览

核心维度Classic Basic PaxosMulti-PaxosRaft
核心抽象对单个值(Single Value)达成决议对一系列日志(Log Stream)连续决议对连续有序的复制状态机日志达成共识
领导者角色对等协商,无固定 Leader(任意 Proposer 可提议)引入稳定 Leader(简化 Phase 1)强领导者 (Strong Leader),日志单向从 Leader 流向 Follower
协商阶段严谨的两阶段:Prepare/Promise $\rightarrow$ Accept/Accepted选举出 Leader 后,可跳过 Phase 1,合并为一阶段 Accept选举时单次投票;日常直接走单向 AppendEntries 流水线
日志空洞 (Log Gaps)允许并发针对不同 Log Index 协商,存在日志空洞需复杂的空洞修补与日志重排逻辑严格无空洞:归纳法保证连续性,冲突直接强行覆盖
活锁问题 (Livelock)高并发下多个 Proposer 相互递增编号提议导致活锁依赖主节点抑制并发竞争随机超时 (Randomized Timeout) 从机制上规避选举死锁
成员变更需针对每轮变更重新跑两阶段共识实现极其晦涩复杂联合共识 (Joint Consensus) 或单节点步进安全过渡
典型工业代表极少裸用Google Chubby、Google Spanner、Apache ZooKeeper (ZAB 衍生)etcd、TiKV、HashiCorp Consul、CockroachDB、Kafka KRaft
D2 Diagram
qtopie.github.io

为什么工业界越来越青睐 Raft?

  1. 消除了状态的二义性与空洞问题: Paxos 允许多个 Proposer 并发对不同 Index 提交提案,导致某些 Index 已经被提交,而前面的 Index 却尚未决议(产生日志空洞)。状态机在遇到空洞时必须挂起等待,恢复与修复逻辑极其繁琐。而 Raft 强制日志必须从前往后连续,前缀不匹配直接拒绝并回退覆盖,大幅简化了状态机模型。
  2. 状态空间急剧收敛: Paxos 算法由于各个角色的逻辑高度解耦和自由,其系统可能处于的状态组合是指数级的爆炸空间。Raft 通过强领导者原则,将权力收拢至单点,使得集群在稳定期内的运行完全退化为线性的单向数据流。
  3. 更贴合工程师思维的工程化完备性: 原始 Paxos 论文甚至没有给出关于成员变更(Membership Change)和日志压缩(Log Compaction)的完备可落地规范,逼得 Google 在工程实现 Chubby 时感慨:“Paxos 论文只描述了 10% 的系统,剩下的 90% 都在处理现实中的工程细节”。而 Raft 在诞生之初,就将选举、复制、安全性、快照截断和成员变更做成了一体化的完整闭环。