引言

在分布式系统中,共识算法是解决多个节点之间数据一致性的核心基石。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发起选举,收集选票,获得多数票后成为 LeaderFollower 选举超时

状态转换的关键时间参数是 选举超时(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):

  1. 如果两个日志条目具有相同的 index 和 term,则它们存储相同的命令
  2. 如果两个日志条目具有相同的 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
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部