一、分布式共识:为什么我们需要 Raft
1.1 CAP 定理与一致性的困境
在分布式系统中,数据可能分布在多个节点上,如何保证这些节点上的数据在面对网络分区、节点故障时仍然保持一致,是分布式系统最核心的挑战之一。CAP 定理告诉我们,网络分区(P)发生时,必须在一致性(C)和可用性(A)之间做出选择。CP 系统(如 etcd、ZooKeeper)选择了一致性,而共识算法就是实现 CP 选择的基石。
1.2 Paxos 的困境
在 Raft 之前,Lamport 的 Paxos 算法是分布式共识的事实标准。然而 Paxos 以其难以理解和实现而闻名 —— 即使在今天,能正确实现 Multi-Paxos 的团队屈指可数。Raft 的作者 Diego Ongaro 在论文中提出了一个明确目标:设计一个比 Paxos 更容易理解的共识算法,同时不牺牲正确性和性能。
Raft 通过"分离"将共识问题分解为三个相对独立的子问题:Leader 选举、日志复制、安全性,使得整个算法的推理路径更加清晰。
二、Raft 核心概念与角色模型
2.1 三种服务器状态
Raft 中每个节点在任意时刻都处于以下三种状态之一:
- Leader:处理所有客户端请求,发起日志复制,定期向 Follower 发送心跳。一个 Term 内最多一个 Leader
- Follower:被动接收 Leader 的请求,不主动发起任何操作。如果选举超时内未收到心跳,转为 Candidate
- Candidate:在选举中发起投票请求,获多数票后成为 Leader
2.2 Term(任期)
时间被划分为一个个 Term(任期),每个 Term 是一个连续递增的 Term 编号。Term 是一次逻辑时钟,用于检测过期的信息。每个节点维护当前的 Term 编号,请求中的 Term 过期的节点会立即拒绝请求。每个 Term 最多选出一个 Leader,但也可能选不出(选票分裂导致无人获多数)。
2.3 RPC 通信原语
Raft 仅使用两种 RPC:
- RequestVote RPC:Candidate 在选举期间发起,携带 Candidate 的 Term、最后日志索引和最后日志 Term
- AppendEntries RPC:Leader 发起,用于日志复制和心跳(不带日志项时为心跳)
三、Leader 选举机制
3.1 触发与流程
当 Follower 的选举计时器(election timeout,通常 150-300ms 随机化)超时未收到 Leader 心跳时,该节点增加自己的 Term、转为 Candidate、投票给自己,并向其他所有节点并行发送 RequestVote RPC。Candidate 会处于三种结局之一:
- 赢得选举:获得集群多数票(N/2+1),立即成为 Leader 并向所有节点广播心跳
- 另一节点成为 Leader:收到更高 Term 的 AppendEntries,Candidate 退为 Follower
- 选举超时:选票分裂导致无人获多数,Term 增加后重新发起选举
3.2 任期随机化与选票承诺
为减少选票分裂的概率,Raft 要求每个节点的选举超时时间在一个范围内随机选择(通常 150-300ms)。这样不太可能两个节点同时超时。此外,每个节点一个 Term 内只能投票给第一个请求的 Candidate,保证每个 Term 最多只有一个 Candidate 能赢得选举。
3.3 日志完整性检查
Candidate 的日志必须至少和大多数节点一样"新"才能当选。RequestVote RPC 包含 Candidate 的最后日志索引和 Term,接收者会比较:如果请求者的日志不比自己新,则拒绝投票。比较规则:先比最后日志 Term,Term 相同则比日志长度。
四、日志复制:Raft 的核心工作
4.1 日志项结构
每个日志项包含三个要素:
- Term:日志项创建时的 Leader Term
- Index:日志项的位置(从1开始)
- Command:客户端提交的命令(状态机指令)
4.2 提交流程
日志复制是 Raft 将客户端命令持久化到多数节点的过程:
- Leader 接收客户端命令,追加到本地日志
- Leader 并行向所有 Follower 发送 AppendEntries RPC(携带新日志项)
- Follower 检查一致性(前一条日志是否匹配),匹配则追加,冲突则删除冲突项及后续所有项
- Leader 收到多数 Follower 的成功响应后,将日志项标记为 Committed,应用到状态机
- Leader 在下次心跳或 AppendEntries 通知 Follower 最新的 CommitIndex
4.3 日志匹配性质
Raft 依赖两个关键的日志匹配性质保证安全性:
- 不同日志中的两个项如果 Index 和 Term 相同,则存储相同的命令
- 不同日志中的两个项如果 Index 和 Term 相同,则之前所有项都相同
五、安全性约束
5.1 选举限制
Raft 不允许一个日志项不是来自多数派的 Leader 被当选。这意味着 Leader 的日志必须包含所有已提交的日志项。
5.2 Leader 不提交之前 Term 的日志
这是 Raft 中最微妙的安全约束。直接提交当前 Term 的日志(因为多数复制即可),但对于之前 Term 的日志,不能仅凭多数复制就提交。这是因为:
考虑一个场景:旧 Leader 将日志复制到多数派后崩溃,新 Leader 和旧 Leader 的多数派有重叠。如果新 Leader 直接提交了旧 Term 的未提交日志,可能会覆盖已经被应用到状态机的正确日志。正确的做法:新 Leader 继续接收新 Term 的日志,当新 Term 的日志提交时,旧 Term 的日志也间接被提交。
5.3 安全性证明
Raft 通过反证法证明 Leader Completeness Property:如果某日志项在某 Term 被提交,则该日志项一定存在于后续所有 Term 的 Leader 日志中。
六、集群成员变更
6.1 联合共识(Joint Consensus)
Raft 使用两阶段成员变更保证配置变更期间不出现双 Leader:
- 联合共识阶段:新配置 Cnew 和 Cold 联合组成过渡配置。所有决策需要 Cold 和 Cnew 中各自多数同意
- 提交 Cnew:联合共识提交后,Leader 广播 Cnew,此后按 Cnew 运行
这种方法避免了整个变更过程中任何节点子集成为多数导致的分脑。
6.2 单节点变更
实际实现(如 etcd raft)中,两阶段可能被简化为每次只增减一个节点。单节点变更保证了新旧配置的多数派一定有重叠。
七、日志压缩与快照
7.1 日志无限增长问题
长时间运行后,日志条目数量会持续增长,占用大量内存,影响恢复速度。Raft 允许 Leader 创建快照:将当前状态机的完整状态序列化为快照,连同最后的日志 Index 和 Term 写入快照。
7.2 InstallSnapshot RPC
当 Leader 需要向一个严重落后的 Follower 同步时,远比日志复制高效的方式是发送快照。Leader 通过 InstallSnapshot RPC 发送快照数据,Follower 收到后重置自身日志并从快照起点恢复。
八、Raft 实现生态与实战应用
8.1 HashiCorp Raft (Go)
HashiCorp 的 Raft 实现是 Go 生态中最完整的参考实现,被 Consul、Vault 等广泛使用。它提供了可插拔的 Transport(TCP/gRPC)、Storage(BoltDB/MDBM)和 Snapshot 机制:
8.2 etcd/raft (Go)
etcd 使用的 Raft 实现是经过高度优化的。它分离了层的职责:
- raft 核心:仅处理消息传递,不直接进行网络 IO
- rafthttp:负责 HTTP 长连接传输 Raft 消息
- wal + snap:日志和快照的持久化层
etcd raft 的特点是使用 Node 接口包装 Raft 状态机,用户只需实现 Ready 接口处理 Ready 事件。
8.3 TiKV/rust-rocksdb (Rust)
TiKV 使用的 Raft 实现是 raft-rs(Rust 语言实现),配合 RocksDB 作为底层存储,成就了 TiDB 的底层分布式 KV 引擎。raft-rs 的特点是内存安全、零拷贝消息编解码,以及深度优化的批量处理。
九、性能优化与生产实践
9.1 批处理与流水线
Raft 的一个重要优化是批处理日志项:Leader 不逐条发送 AppendEntries,而是积累一批后一次性发送。配合流水线(pipeline)机制:发送第一条 AppendEntries 后不等响应就发送第二条,网络利用率大幅提升。
9.2 ReadIndex 与 LeaseRead
读请求如果走 Raft 日志会严重影响性能。Raft 提供两种优化方案:
- ReadIndex:Leader 发起一轮心跳确认自己仍是多数派 Leader,确认后可直接读状态机
- LeaseRead:基于 Leader 租约(lease),在租约期内(通常选举超时一半)直接读状态机
9.3 Pre-Vote 优化
网络分区中的节点恢复后,如果立即发起选举,会打断当前 Leader 工作。Pre-Vote 优化要求 Candidate 先在预投票阶段确认能否获多数支持,只有确有可能才真正增加 Term 发起选举。
十、总结
Raft 之所以成为工业界共识算法的事实标准,关键在于它在正确性、性能和可理解性之间取得了良好平衡。理解 Raft 的关键在于掌握三个子问题的解法,尤其是日志复制与安全性约束之间的交互。
在生产环境中部署 Raft 集群时,需要注意选举超时设置、日志批量处理、快照策略、网络抖动处理等细节。目前 etcd、TiKV、Consul、CockroachDB 等系统都运行着 Raft,它是构建可靠分布式系统的强大基石。
进一步学习建议:阅读 Raft 论文的 Figure 2(精确定义)和 Raft 博士论文的 Chapter 3(实现细节),尝试自己动手实现一个 mini-Raft,体验各模块的工程设计取舍。

发表评论 取消回复