一、为什么需要分布式一致性?
在分布式系统中,数据存储在多台机器上,如何保证所有副本在同一时刻看到相同的状态,是一个核心挑战。当某个节点宕机、网络分区或消息延迟时,系统仍能正确运转——这就是分布式一致性协议要解决的问题。
常见的一致性级别包括:强一致性(线性一致性)、顺序一致性、因果一致性和最终一致性。Raft 协议提供的是强一致性保证,确保客户端读到的总是最新已提交的数据。
二、Raft 的设计哲学:可理解性优先
Raft 诞生于 2014 年,由 Diego Ongaro 和 John Ousterhout 在论文《In Search of an Understandable Consensus Algorithm》中提出。相比 Paxos 的"难以理解",Raft 将共识问题分解为三个相对独立的子问题:
- Leader 选举:选出集群的"话事人"
- 日志复制:确保所有节点拥有相同的操作序列
- 安全性:保证已提交的日志不会被覆盖
通过"状态机复制"(Replicated State Machine)模型,Raft 将业务逻辑抽象为确定性状态机,只要输入序列一致,所有节点的最终状态就必然一致。
三、Raft 核心概念与术语
3.1 节点角色
Raft 将节点分为三种角色:
- Leader(领导者):处理所有客户端请求,管理日志复制,定期发送心跳维持权威
- Follower(跟随者):被动响应 Leader 的心跳和日志追加请求
- Candidate(候选人):选举临时状态,发起投票请求以竞争 Leader
3.2 任期(Term)机制
时间被划分为连续的任期(Term),每个 Term 是一个逻辑时钟,单调递增。Term 的作用是:
- 标识 Leader 的合法性(同一 Term 只有一个 Leader)
- 检测过期信息:节点收到旧 Term 的消息时直接拒绝
- 驱动角色转换:发现更高 Term 时自动降级为 Follower
3.3 四大核心 RPC
| RPC 类型 | 发起方 | 用途 |
|---|---|---|
| RequestVote | Candidate | 竞选 Leader,请求选票 |
| AppendEntries | Leader | 日志复制 心跳(空条目即为心跳) |
| InstallSnapshot | Leader | 快照传输,用于追赶落后太多的 Follower |
| TimeoutNow | Leader | 主动触发转移(可选实现) |
四、Leader 选举详解
4.1 选举触发条件
Follower 会在选举超时(通常 150-300ms 随机化)内未收到 Leader 心跳时发起选举。随机超时机制是为了避免多个节点同时发起选举导致"选票分裂"(Split Vote)。
4.2 选举流程
- Follower 自增 Term,转为 Candidate
- 投给自己一票,向所有节点广播 RequestVote
- 收到大多数(N/2 1)选票后成为 Leader
- 立即发送心跳,阻止其他节点发起新选举
4.3 安全性约束:日志至少一样新
RequestVote RPC 包含候选人最后日志的 Term 和 Index。选民会检查:
- 候选人最后日志 Term < 我的最后日志 Term → 拒绝
- 候选人最后日志 Term == 我的,但 Index < 我的 → 拒绝
这保证了当选 Leader 一定包含所有已提交日志。
五、日志复制机制
5.1 正常流程
客户端请求到达 Leader 后:
- 将操作追加为日志条目(未提交状态)
- 通过 AppendEntries RPC 并行发送给所有 Follower
- 大多数节点确认后,Leader 提交该条目
- 提交后应用到状态机,返回结果给客户端
- 下次心跳通知 Follower 提交
5.2 日志一致性检查与修复
AppendEntries 包含前一条日志的 Term 和 Index 用于一致性检查。当 Follower 在对应位置找不到匹配的条目时,Leader 会递减 NextIndex逐步后退,直到找到一致点,然后覆盖后续日志。这种"回退"机制在日志分叉时尤为重要。
5.3 提交规则:不能提交之前任期的日志
这里有一个极易踩坑的细节:Leader 不能直接提交之前 Term 的日志条目。原因是 Leader 的提交是基于"当前 Term 有条目被确认"来递增 commitIndex 的。如果旧 Term 的条目被直接提交,在后续复制时可能被覆盖(例如前任 Leader 的未提交条目被新 Leader 覆盖)。
正确做法:Leader 在当前 Term 有新条目被大多数确认后,就可以安全地提交当前及之前所有条目(因为当前 Term 条目的提交隐含保证了之前所有条目的一致性)。
六、安全性保证
6.1 Election Restriction(选举限制)
已如前述,候选人日志必须"至少与投票者一样新"。
6.2 Commitment Rule(提交规则)
如 5.3 节所述,通过间接提交保证安全性。
6.3 Leader Completeness Property
某个日志条目在某个 Term 被提交,则该条目必然存在于所有更高 Term 的 Leader 中。这是 Raft 最核心的安全定理。
七、快照与日志压缩
长期运行的系统中,日志无限增长会带来内存和恢复时间的压力。Raft 通过快照(Snapshot)机制解决:
- 当日志达到阈值时,Leader 生成快照,包含当前状态机状态和最后包含的日志的 Term/Index
- 旧日志条目可以被安全删除
- 当 Follower 落后太多时,Leader 通过 InstallSnapshot RPC 发送快照
快照实现的关键是保证快照最后包含元数据的正确性,以及处理快照传输期间的并发修改。
八、成员变更(Joint Consensus)
集群不能停机扩容缩容。Raft 使用两阶段成员变更(Joint Consensus):
- 切换到"联合配置"(Cold ∪ Cnew),所有决策需同时获得旧配置和新配置的大多数同意
- 联合配置提交后,切换到新配置 Cnew
这避免了在过渡期间两个配置各自形成多数派导致脑裂的问题。
九、工程实践中的关键细节
9.1 Pre-Vote 协议
一个与网络隔离的节点恢复后会不断发起选举(Term 递增),但无法获得选票,导致集群 Term 无限增长。Pre-Vote 协议要求候选人先发起一轮"预投票",只有在获得大多数同意后才会自增 Term 发起正式选举。
9.2 Checkquorum 与 Lease Read
Leader 需要确认自己仍是 Leader(没有被网络分区隔离)。Lease Read 通过心跳确认 时钟偏移保证,在租约期内直接读取本地状态,无需走日志提交。
9.3 Leadership Transfer(领导权转移)
在节点计划维护前或均衡负载时,主动将 Leader 转移给指定节点。步骤为:
① Leader 暂停服务
② 发送 TimeoutNow给目标节点
③ 目标节点立即发起选举
④ 由于日志最新,该节点必然当选
9.4 批量优化与流水线
生产环境中,连续日志条目可以批量发送,减少网络往返。AppendEntries 处理采用流水线:不等前一个 RPC 返回就发送下一个,提高吞吐量。
十、Raft 实现框架对比
| 项目 | 语言 | 特点 |
|---|---|---|
| etcd/raft | Go | 生产级,Kubernetes 核心组件 etcd 的底层共识 |
| TiKV (raft-rs) | Rust | 高性能,PingCAP 出品,async/await |
| braft | C | 百度出品,支持多组 Raft |
| jraft | Java | Apache 生态集成 |
| SOFAJRaft | Java | 蚂蚁金服出品,多组、全功能 |
十一、总结
Raft 通过"分解问题 可理解性优先"的设计,成为目前最广泛使用的分布式一致性协议。从 etcd 到 TiKV、从 Consul 到 CockroachDB,Raft 已是构建可靠分布式系统的基础设施。
理解 Raft 不仅是面试加分项,更是设计高可用系统的必备内功。核心记住三件事:选举靠超时 多数票,日志靠 AppendEntries 一致性检查,安全性靠"当前 Term 提交带动历史提交"。
参考资料:Ongaro

发表评论 取消回复