写在前面
分布式系统里"多个节点对一件事达成一致"听起来简单,做对极难——这就是**共识算法(Consensus)**要解决的问题。
最经典的共识算法是 Paxos——Lamport 1998 年的论文。但 Paxos 论文有个臭名昭著的问题:没人看得懂。Lamport 后来又写了"Paxos Made Simple"试图解释——还是难懂。
2014 年 Stanford 的 Diego Ongaro 和 John Ousterhout 提出了 Raft 算法——明确目标是"可理解性优先"。论文标题就是 In Search of an Understandable Consensus Algorithm。
Raft 现在是工业界事实标准——etcd、Consul、TiKV、CockroachDB、HashiCorp 全家桶都在用。本文用直觉的方式把 Raft 讲清楚——不写公式,但讲清核心机制和工程意义。
一、共识算法在解决什么问题
设想 5 台机器要一起维护一份日志:
| |
某些机器宕机、网络分区、消息延迟——怎么保证所有活着的机器最终都看到同一份日志?
共识算法要保证:
- Safety(安全性):即使发生故障,所有节点的状态不会冲突
- Liveness(活性):只要多数节点存活,系统能继续工作
- Consistency(一致性):相同输入序列产生相同输出
Raft 用三个核心机制实现这些性质——领导选举、日志复制、安全性。
二、Raft 的角色
集群里每个节点处于三种状态之一:
- Leader:唯一的"指挥官",处理所有客户端请求
- Follower:被动响应 Leader 和 Candidate 的消息
- Candidate:选举期间的临时角色
任何时刻只有一个 Leader——这是 Raft 简化的关键。所有写入都通过 Leader——Leader 把变更复制到 Followers——Follower 投票确认——Leader 提交并通知所有人。
三、领导选举
集群启动时所有节点都是 Follower。每个节点有一个随机选举超时(150-300ms):
- Follower 在超时内没收到 Leader 心跳 → 转为 Candidate
- Candidate 给自己投一票,向其他节点发送"选我"请求
- 其他节点收到后:
- 如果当前 term 没投过票 → 投票给它
- 如果已投过票 → 拒绝
- Candidate 收到多数票(n/2+1)→ 成为 Leader
- 新 Leader 立刻发心跳,宣告自己存在
为什么要随机超时
如果所有节点超时一致——所有节点同时变 Candidate,互相投票冲突,没人能赢。随机化让某个节点先到超时点——它最早开始拉票,更容易胜出。
Term 概念
Raft 把时间分成"任期"(Term)——每次选举触发新的 term。Term 是单调递增的整数。
- Leader 每次发心跳带上自己的 term
- Follower 收到更高 term → 降级、跟随新 Leader
- 旧 Leader 网络恢复后看到更高 term → 自动退位
Term 解决了"脑裂后旧 Leader 还以为自己是 Leader"的问题——所有响应都校验 term,更高 term 自动获胜。
四、日志复制
Leader 收到客户端写请求后:
- 写入自己的日志
- 并行发给所有 Follower(AppendEntries RPC)
- 多数 Follower 确认收到 → 提交(commit)
- 下次心跳通知 Follower 提交
- 提交后应用到状态机,回复客户端
日志一致性
每条日志带上 (term, index)——Follower 收到 AppendEntries 时检查:
- 上一条日志的 (term, index) 和我本地的对得上吗?
- 对得上 → 追加新日志
- 对不上 → 拒绝,让 Leader 退一格再发
Leader 维护每个 Follower 的 nextIndex——发现 Follower 跟不上时逐步回退、重发,直到对齐。
五、安全性约束
光有选举和复制不够——必须保证已提交的日志永远不丢。Raft 通过几个约束实现:
1. 选举限制:只有"日志最新"的节点能赢
Candidate 拉票时附带自己最后一条日志的 (term, index)。Follower 只投票给"日志比自己新或一样新"的 Candidate。
这保证新 Leader 一定包含所有已提交的日志——不会有"新 Leader 上任后丢掉之前已提交的事"。
2. 提交限制:只能提交当前 term 的日志
Leader 不能直接提交"上一个 term 的日志"——只能等当前 term 有日志被多数复制后,带着把之前的日志一起提交。
这避免了一个微妙的 bug——Lamport 的 Paxos 也有类似机制,但 Raft 把它讲得更清楚。
3. AppendEntries 一致性检查
每次发日志都带上前一条日志的 (term, index)——Follower 检查匹配后才追加。这是一种简单粗暴但有效的对齐方式。
六、几个常见问题
1. 网络分区怎么办
5 节点集群被分成 [3, 2]:
- 3 节点的子集仍可选举出 Leader、继续对外服务(多数派)
- 2 节点子集无法选出新 Leader、只能等待
分区恢复后——2 节点子集发现更高 term,自动跟随大集群的 Leader。未提交的事务被覆盖。
2. Leader 故障
Leader 宕机后心跳停止——某个 Follower 选举超时变 Candidate——选出新 Leader——继续工作。通常 1-2 秒内完成切换。
3. 客户端读
朴素做法:所有读都走 Leader——保证强一致,但 Leader 压力大。
优化方案:
- ReadIndex:读时让 Leader 确认自己仍是 Leader(一次心跳),然后本地读
- Lease Read:Leader 持有"租约",租约期内本地读
- Follower Read:允许从 Follower 读(弱一致,但快)
4. 集群成员变更
加节点 / 减节点这种"在运行中变更集群配置"——Raft 用两阶段共识(Joint Consensus):
- 先达成"新旧配置共存"的共识
- 再切到纯新配置
避免了"新旧多数派同时存在导致两个 Leader"的脑裂。
5. 日志压缩
日志一直涨会爆磁盘——Raft 用快照(Snapshot):
- 周期性把状态机全量存盘
- 旧日志可以删除
- 新加入的节点先拉快照,再追日志
七、Raft 的工业实现
etcd
Kubernetes 的元数据存储——5 节点 Raft 集群最常见。高可用 + 强一致——但写性能不高(每次都要多数派 ack)。
Consul
HashiCorp 的服务发现——同样 5 节点 Raft。
TiKV
PingCAP 的分布式 KV——每个数据 region 独立 Raft——上千个 Raft 组并行。
CockroachDB
SQL 数据库底层用 Raft 复制每个 range。
Apache Ratis
通用 Raft 库(Java)——OZone、Apache Iceberg 等用它。
MIT 6.824 的 Lab
学 Raft 最好的方式是自己实现一遍——MIT 6.824 的 Lab 2A/2B/2C 就是手写 Raft。
八、Raft vs Paxos vs Multi-Paxos
| Paxos | Multi-Paxos | Raft | |
|---|---|---|---|
| 出处 | Lamport 1998 | 同上扩展 | Ongaro 2014 |
| 角色 | Proposer/Acceptor/Learner | 同上 | Leader/Follower/Candidate |
| 强 Leader | ✗ | ✓ | ✓ |
| 可理解性 | 低 | 中 | 高 |
| 工业实现 | Chubby 早期文献描述 | Chubby / Spanner / Megastore | etcd / Consul / TiKV / 几乎所有新系统 |
关于 ZooKeeper:ZooKeeper 用的是 ZAB(ZooKeeper Atomic Broadcast)——不是 Multi-Paxos,而是一种 Paxos 变体,针对"主备复制 + 严格按序广播"做了简化。ZAB 和 Raft 的设计哲学接近(强 Leader + 心跳 + 单调递增的 epoch / term),但出现得更早,是 ZK 自家的方案。
Raft 在功能上和 Multi-Paxos 等价——但可理解性、可实现性、可调试性都强很多。这就是它后来居上的核心原因。
九、什么时候用 Raft、什么时候不用
✅ 适合:
- 需要强一致性的元数据存储(K8s 配置、服务发现)
- 数据库的元数据复制(schema、租约)
- 小规模集群(3-7 节点)
- 写入不频繁但要绝对可靠
❌ 不适合:
- 大规模数据复制(百 GB 数据)——延迟高
- 极高写入吞吐场景——多数派 ack 是瓶颈
- 跨数据中心——网络延迟让心跳和投票变得脆弱
- AP 系统(如 Cassandra)——选 P + A 而不是 C,根本不需要共识
最低要 3 节点(容忍 1 个故障)、推荐 5 节点(容忍 2 个)——永远不要 1 节点 Raft,那等于没用。
十、对工程的启发
Raft 不只是"一个算法"——它示范了几个值得借鉴的工程哲学:
- 可理解性是设计目标——不只是"能 work",要"能讲清"
- 强 Leader 简化系统——比起完全对称的 Paxos,单 Leader 让推理更容易
- 机制分离:选举、复制、安全性各自独立讲清楚
- 拒绝复杂特性:Raft 的论文明确说"拒绝任何不必要的复杂"
设计任何分布式系统时——理解 Raft 都是入门必修课。
小结
把全文压一句:
Raft 用『强 Leader + 多数派复制 + 严格安全约束』把分布式一致性讲得像故事——可理解性是它最大的成就,可工业化是它的最大遗产。
学习路径推荐:
- 读 Raft 论文:raft.github.io(强烈推荐)
- 看可视化:The Secret Lives of Data
- 手写实现:MIT 6.824 Lab 2
- 用一遍 etcd / Consul
掌握 Raft 后再看 ZooKeeper、ZAB、Spanner 的 Multi-Paxos——能立刻看出它们解决问题的相同与不同。