引言
在分布式系统中,共识算法是解决多个节点之间数据一致性的核心基石。Raft 算法由 Diego Ongaro 和 John Ousterhout 在 2014 年的论文《In Search of an Understandable Consensus Algorithm》中提出,旨在成为比 Paxos 更易于理解和工程实现的共识协议。如今,Raft 已广泛应用于 etcd、TiKV、Consul、CockroachDB 等关键基础设施中。
一、Raft 核心概念与角色状态机
Raft 将共识问题分解为三个相对独立的子问题:Leader 选举、日志复制和安全性保证。集群中的每个节点始终处于以下三种状态之一:
| 角色 | 职责 | 触发条件 |
|---|---|---|
| Leader | 处理所有客户端请求、管理日志复制、发送心跳 | 选举超时后获得多数选票 |
| Follower | 被动响应 Leader/Candidate 的 RPC,超时则转 Candidate | 默认初始状态 |
| Candidate | 发起选举,收集选票,获得多数票后成为 Leader | Follower 选举超时 |
状态转换的关键时间参数是 选举超时(Election Timeout),通常在 150ms~300ms 之间随机化,这是避免选票分裂(Split Vote)的第一道防线。
二、Leader 选举机制详解
当一个 Follower 在选举超时窗口内未收到 Leader 的心跳 AppendEntries RPC 时,它便自增当前 term、投票给自己、重置选举计时器,然后向所有其他节点发送 RequestVote RPC。
投票遵循每条任期最多投一票的原则(每个 term 内每个节点只投给第一个请求者)。此外,Raft 通过日志至少一样新(Log Up-to-Date)规则防止数据丢失的节点成为 Leader:Candidate 的日志必须不比投票者旧的(比较最后一条日志的 term 和 index)。
2.1 选票分裂与随机化
当多个 Follower 同时超时转 Candidate 时,可能出现选票分裂(Split Vote)——每个 Candidate 都只获得部分选票而无法达到多数。Raft 通过以下策略缓解:
- 随机选举超时:每个节点在 [T, 2T] 范围内随机选取超时值,大幅降低同时超时的概率
- Pre-Vote 扩展(Raft 论文 9.6 节):在正式递增 term 前先试探性请求投票,避免因网络分区导致 term 无限膨胀
三、日志复制与提交机制
Leader 选举完成后,进入日志复制阶段。客户端的写请求被封装为日志条目(Log Entry),由 Leader 按顺序追加到自己的日志中,然后通过 AppendEntries RPC 并行复制给所有 Follower。
3.1 日志匹配特性
Raft 保证以下日志匹配特性(Log Matching Property):
- 如果两个日志条目具有相同的 index 和 term,则它们存储相同的命令
- 如果两个日志条目具有相同的 index 和 term,则之前所有条目都相同
Leader 在发送 AppendEntries 时附带 prevLogIndex 和 prevLogTerm,Follower 会检查一致性——若不匹配则拒绝,Leader 递减 index 重试直至找到一致点。
3.2 提交(Commit)规则
一个日志条目被提交(committed)当且仅当 Leader 已将其复制到多数节点。Leader 维护 commitIndex(已提交的最高索引),并在下次心跳中携带给 Follower。
关键约束:Leader 不能仅凭多数复制提交之前任期的日志条目。必须等到当前任期的日志条目被多数复制后,才能顺带提交之前任期的未提交条目。这个规则防止了论文 5.4.2 节中描述的 Figure 8 问题。
四、安全性保证
4.1 选举限制
Raft 保证从 Leader 当选那一刻起,所有已提交的日志条目都已存在于 Leader 的日志中。这是通过上述的"日志至少一样新"投票规则保证的。
4.2 Leader 完整性属性
如果一个日志条目在某个 term 被提交,则该条目必然存在于所有更高 term 的 Leader 日志中。这是前面两条规则的直接推论。
4.3 状态机安全性
如果一个节点已将某 index 的日志条目应用到状态机,则不会有其他节点在相同 index 应用不同的条目。这由日志提交规则保证。
五、集群成员变更——联合共识
集群配置变更(增加/删除节点)是最容易出错的场景。Raft 采用联合共识(Joint Consensus)算法,通过一个过渡配置 C(old,new) 来保证安全性:进入联合共识阶段要求旧配置和新配置各自的多数都必须同意,切换到纯新配置后按新规则运行。
工程实践中,TiKV 的 raft-rs 和 etcd 采用了单步变更(One-Subscriber-at-a-Time)变体,每次只增减一个节点——将联合共识简化但同时保证安全性。
六、快照与日志压缩
在实际系统中,日志无限增长不可持续。Raft 通过快照(Snapshot)机制进行日志压缩。当日志超过阈值时,Leader 为每个落后的 Follower 发送 InstallSnapshot RPC,包含 last included index/term、集群配置、状态机快照数据。
快照协议也用于 Leader 向新加入或严重落后的 Follower 同步数据——当 Follower 需要的日志条目已被压缩时,Leader 直接发送快照。
七、线性化读与 ReadIndex/LeaseRead
Raft 的日志复制只保证写操作的线性化。读操作有几种实现策略:
- ReadIndex:Leader 记录当前 commitIndex,等待一次心跳确认自己仍是 Leader 后返回读(一次网络往返)
- LeaseRead:Leader 在心跳租约期内无需确认,直接读(最低延迟,依赖时钟精度)
- ReadIndex + Quorum:向多数节点请求已应用的 index,取最大值等待(容错时钟漂移)
etcd 使用 LeaseRead(基于 Leader 的选举超时作为租期),TiKV 使用 ReadIndex + 向多数 Follower 确认。
八、工程实践中的优化技巧
8.1 批处理与流水线
Leader 不逐条等待 AppendEntries 响应,而是维护每个 Follower 的 nextIndex 和 matchIndex,连续发送日志条目——实现流水线化复制,大幅降低高延迟网络下的吞吐损失。
8.2 预写日志(WAL)与批量 fsync
工程实现通常使用预写日志(Write-Ahead Log)配合组提交(Group Commit)——在 fsync 间隔内积累多个条目一次性刷盘,在不牺牲持久性的前提下最大化吞吐。
8.3 Learner 节点
Raft 论文引入 Learner(非投票)节点:接收日志复制但不参与选举和提交计数。适用于只读副本、跨地域容灾等场景。etcd 和 TiKV 均支持 Learner。
九、Raft vs Paxos:工程视角的选择
在工程实践中选型时需考虑:Raft 可理解性更强,实现代码通常比 Paxos 短 3-5 倍;etcd/raft-rs 等高质量实现降低了自研风险;大多数新项目应优先选择 Raft。除非有极端性能需求或已有 Paxos 实现积累。
十、总结
Raft 通过角色分解(Leader/Follower/Candidate)、强 Leader 模型和清晰的子问题划分,使共识算法从理论走向可工程化实现。理解 Raft 的关键在于把握三个核心约束:多数派提交保证安全性、日志匹配保证一致性、选举限制保证 Leader 完整性。
在现代云原生基础设施中,Raft 几乎是默认的共识选择。深入理解其工程细节——不仅是论文中的理论算法——对于构建高可靠的分布式系统至关重要。
参考资料
- Ongaro D, Ousterhout J. In Search of an Understandable Consensus Algorithm. USENIX ATC 2014.
- Raft 可视化学习网站: raft.github.io
- etcd raft 实现: github.com/etcd-io/raft

发表评论 取消回复