Featured image of post 浅谈 Raft:让分布式一致性变可理解的算法

浅谈 Raft:让分布式一致性变可理解的算法

Paxos 难懂,Raft 把分布式一致性讲清楚了。本文用直觉解释 Raft 的领导选举、日志复制、安全性

写在前面

分布式系统里"多个节点对一件事达成一致"听起来简单,做对极难——这就是**共识算法(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 台机器要一起维护一份日志:

1
2
3
4
5
Machine 1: [A, B, C, D]
Machine 2: [A, B, C, ?]
Machine 3: [A, B, C, D, E]
Machine 4: [A, B, ?, ?]
Machine 5: [A, B, C, D]

某些机器宕机、网络分区、消息延迟——怎么保证所有活着的机器最终都看到同一份日志?

共识算法要保证:

  • Safety(安全性):即使发生故障,所有节点的状态不会冲突
  • Liveness(活性):只要多数节点存活,系统能继续工作
  • Consistency(一致性):相同输入序列产生相同输出

Raft 用三个核心机制实现这些性质——领导选举、日志复制、安全性


二、Raft 的角色

集群里每个节点处于三种状态之一:

  • Leader:唯一的"指挥官",处理所有客户端请求
  • Follower:被动响应 Leader 和 Candidate 的消息
  • Candidate:选举期间的临时角色

任何时刻只有一个 Leader——这是 Raft 简化的关键。所有写入都通过 Leader——Leader 把变更复制到 Followers——Follower 投票确认——Leader 提交并通知所有人。


三、领导选举

集群启动时所有节点都是 Follower。每个节点有一个随机选举超时(150-300ms):

  1. Follower 在超时内没收到 Leader 心跳 → 转为 Candidate
  2. Candidate 给自己投一票,向其他节点发送"选我"请求
  3. 其他节点收到后:
    • 如果当前 term 没投过票 → 投票给它
    • 如果已投过票 → 拒绝
  4. Candidate 收到多数票(n/2+1)→ 成为 Leader
  5. 新 Leader 立刻发心跳,宣告自己存在

为什么要随机超时

如果所有节点超时一致——所有节点同时变 Candidate,互相投票冲突,没人能赢。随机化让某个节点先到超时点——它最早开始拉票,更容易胜出。

Term 概念

Raft 把时间分成"任期"(Term)——每次选举触发新的 term。Term 是单调递增的整数。

  • Leader 每次发心跳带上自己的 term
  • Follower 收到更高 term → 降级、跟随新 Leader
  • 旧 Leader 网络恢复后看到更高 term → 自动退位

Term 解决了"脑裂后旧 Leader 还以为自己是 Leader"的问题——所有响应都校验 term,更高 term 自动获胜。


四、日志复制

Leader 收到客户端写请求后:

  1. 写入自己的日志
  2. 并行发给所有 Follower(AppendEntries RPC)
  3. 多数 Follower 确认收到 → 提交(commit)
  4. 下次心跳通知 Follower 提交
  5. 提交后应用到状态机,回复客户端

日志一致性

每条日志带上 (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):

  1. 先达成"新旧配置共存"的共识
  2. 再切到纯新配置

避免了"新旧多数派同时存在导致两个 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

PaxosMulti-PaxosRaft
出处Lamport 1998同上扩展Ongaro 2014
角色Proposer/Acceptor/Learner同上Leader/Follower/Candidate
强 Leader
可理解性
工业实现Chubby 早期文献描述Chubby / Spanner / Megastoreetcd / 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 不只是"一个算法"——它示范了几个值得借鉴的工程哲学:

  1. 可理解性是设计目标——不只是"能 work",要"能讲清"
  2. 强 Leader 简化系统——比起完全对称的 Paxos,单 Leader 让推理更容易
  3. 机制分离:选举、复制、安全性各自独立讲清楚
  4. 拒绝复杂特性:Raft 的论文明确说"拒绝任何不必要的复杂"

设计任何分布式系统时——理解 Raft 都是入门必修课


小结

把全文压一句:

Raft 用『强 Leader + 多数派复制 + 严格安全约束』把分布式一致性讲得像故事——可理解性是它最大的成就,可工业化是它的最大遗产。

学习路径推荐:

  1. 读 Raft 论文raft.github.io(强烈推荐)
  2. 看可视化The Secret Lives of Data
  3. 手写实现:MIT 6.824 Lab 2
  4. 用一遍 etcd / Consul

掌握 Raft 后再看 ZooKeeper、ZAB、Spanner 的 Multi-Paxos——能立刻看出它们解决问题的相同与不同。

使用 Hugo 构建
主题 StackJimmy 设计