Raft — 易理解的共识算法
是什么
Raft 是一套让多台机器对”下一步该做什么”达成一致的算法。日常类比:像一个工厂车间——多个工人围着装配线决定下一步装哪个零件,谁也不能擅自决定。Raft 的解决办法是:先投票选一个领班,领班说做啥就做啥;领班挂了大家再投票选下一个。
为什么需要?想象一下:
- 你有 5 台数据库服务器,写一条数据要写到几台才算”写成功”?
- 如果其中两台同时宣称”我才是主”,写到不同台的数据冲突了怎么办?
paxos 几十年前就解决了这个问题,但论文太抽象,连博士生都看不懂。Raft 是 2014 年用通俗版重新发明的同一个轮子——同样的安全性,但每一步都画给你看。
为什么重要
不知道 Raft,下面这些事都没法解释:
- K8s 为什么不会脑裂:K8s 的元数据存在 etcd,etcd 用 Raft 保证多台机器对”集群状态”看法一致
- Consul / TiKV / CockroachDB / RethinkDB 这些”分布式数据库”凭什么敢说”主挂了能自动切”——底层全是 Raft
- 2014 年之前,“分布式共识”是博士论文级话题;之后本科生上完一门课就能实现一个 Raft
- 论文标题本身就是它的核心价值——Understandable(易理解)。这是历史上第一篇把”可读性”当成研究贡献的系统论文
更关键的:完整正文 9 页 + 算法伪代码 50 行就讲完了。复现门槛低到工程师周末能写一个原型。
核心要点
Raft 把共识问题拆成 3 个独立子问题——这是它比 Paxos 易懂的根本原因:
选领班(Leader Election)
每台机器随机等 150-300 毫秒,谁先超时谁就喊”我要当领班”,向其他机器要票。拿到过半票就当选。随机化是关键——如果大家同时喊,永远选不出来。
领班发任务(Log Replication)
领班接到客户端请求,先写到自己的”任务清单”(log),然后并行发给所有副手。过半副手确认收到,这条任务才算 commit(可执行)。
领班挂了不丢任务(Safety)
哪怕领班挂了、网络分裂了、机器重启了,已经 commit 的任务永远不会消失。新选出来的领班一定包含所有已 commit 的任务——这条强约束叫 Election Restriction,是 Raft 安全性的核心。
实践案例
案例 1:5 节点正常选举
初始:A B C D E 都是 follower,term=0
1. A 的 timer 先到,切 candidate,term=1,给自己投一票2. A 向 B C D E 发 RequestVote3. B C D 收到票请求,写盘后回复 yes(majority = 3 票,含 A 自己)4. A 当选 leader,向所有人发心跳,宣告权威案例 2:领班宕机后切换
1. A 当 leader 跑了一阵,复制了若干 log entry 到 B C D E2. A 突然断电3. B C D E 都收不到心跳,等 150-300 ms 各自超时4. C 先超时,term=2,向其他人要票5. D E 投给 C,C 当选6. C 接管,把 A 没复制完的 log 继续推给 follower案例 3:少数派分区静默
5 节点:A B C | D E(中间网络断了)
- 左侧 3 个仍能选出 leader(majority = 3 个)→ 继续服务- 右侧 2 个永远选不出 leader(拿不到 3 票)→ 完全停摆- 网络恢复后,右侧自动追上左侧的 log这就是 Raft 在分区下的取舍:少数派必须停服务,绝不允许两边都写。
踩过的坑
-
“多数派”是 ⌈(n+1)/2⌉ 不是 n/2:n=5 是 3 个,n=4 也是 3 个。所以偶数节点没好处——4 节点和 3 节点一样只能容忍 1 台挂,但要多花一台机器的钱。生产里几乎不见 4 节点 Raft。
-
少数派必须停服务:网络分区时,被分到少数那侧的客户端会发现”我请求都超时”。这不是 bug,是 split-brain 的代价——宁可暂时不可用,也不能让两个 leader 各自写各自的。
-
Log Compaction 论文留作 exercise:log 一直涨重启会回放到天荒地老,必须做 snapshot。但论文只给了大致框架,工业实现要再写 1000 行(snapshot 怎么 fork、怎么传输、怎么和 application state 协同)。etcd 和 TiKV 各写各的。
-
Joint Consensus 配置变更极易写错:从 3 节点扩到 5 节点不能直接切,要走”双 majority”中间态。论文方案、Diego 博士论文方案、后来 Heidi Howard 找到的 corner case——三个版本各有 bug。工业实现互相不兼容。
-
跨 term 直接 commit 旧 entry 是隐藏陷阱:刚选上 leader 不能直接 commit 上一任 leader 留下的 entry,必须先在自己 term 内 commit 一条新 entry,旧 entry 才连带 commit。etcd 早期版本踩过这个坑。
适用 vs 不适用场景
适用:
- 3-5 节点的元数据/配置存储(K8s 的 etcd 经典场景)
- 跨数据中心 quorum < 100ms RTT 的同城多机房
- 状态机要求严格 linearizable read/write 的小规模数据库
不适用:
- 需要扛住几百 TB 写入的大集群——单 leader 是瓶颈,要拆 Multi-Raft
- 跨洲部署(RTT 200ms+)——每次 commit 至少一个 RTT,写延迟受不了
- 拜占庭容错(节点会撒谎)——Raft 假设节点要么正常要么宕机,不防作恶节点,得用 PBFT/HotStuff
历史小故事(可跳过)
- 1989 年:Lamport 写出 Paxos 论文。学界看了 10 年没人懂,2001 年 Lamport 自己又写一篇 Paxos Made Simple 试图讲清楚,结果还是没人懂。
- 2006-2012 年:Google Chubby、Spanner 在生产跑 Multi-Paxos,公开承认”论文到工业实现的鸿沟巨大”。
- 2013 年:Stanford 博士生 Diego Ongaro 把 Raft 草稿拿给 OS 课学生当教材。CoreOS 同年开源 etcd,直接基于这版草稿。
- 2014 年:Diego 博士答辩 + USENIX ATC 论文 + 最佳论文奖。论文里做了 43 个学生对照实验——一组学 Paxos、一组学 Raft,做同份 quiz,Raft 组平均高 23 分(满分 60)。第一篇用人因实验当 evaluation 的共识论文。
- 2015 年起:Kubernetes 把 etcd 当 control plane → Raft 一夜成事实标准。HashiCorp/raft、TiKV、CockroachDB 陆续跟进。
- 2024 年:Kafka 4.0 默认 KRaft,把 ZooKeeper(基于 2010 年的 Zab)替换掉。Raft 完成对工业共识层的全面接管。
学到什么
-
“可理解”本身可以是研究贡献——当一个领域已经有正确性证明完备的协议(Paxos)时,下一个突破点不是”更正确”,是”更可教学 + 更可抄写”
-
强约束换可读性:Raft 比 Paxos 多了显式 leader、单调 term、Election Restriction、log 一致性检查——每条都让协议自由度降低,但每条都让”实现一次写对”的概率提高
-
学术引用 ≠ 工业普及:Paxos 在 Google Scholar 引用比 Raft 高,但工业事实标准是 Raft。10 年时间证明:被引和被用是两件事
-
拆问题是降低复杂度的根本武器:Raft 把共识拆成 election + replication + safety 三个能独立讨论的子问题,Paxos 是一锅炖
延伸阅读
- 论文 18 页扩展版:Ongaro 2014(短版 14 页 + 扩展 safety 证明 4 页)
- 可视化教学站:The Secret Lives of Data — Raft(鼠标点一下跑一步,肉眼看 election 怎么收敛)
- Diego 博士论文 200+ 页:Consensus: Bridging Theory and Practice(论文版省略的所有细节都在这里)
- 工业代码:etcd
raft/raft.go把stepLeader / stepFollower / stepCandidate三种状态拆成三个函数,对照论文 Figure 2 阅读,100% 还原 - 自测站:Raft scope.io(交互式动画,可手动制造分区与重连)
关联
- paxos —— Raft 的精神前辈;同样靠 quorum intersection 保安全,但隐式 leader 和对称 ballot 让工程实现极困难,Raft 是它的工程化版本
反向链接
- apollo-2014 —— Apollo — 让两万台机器自己决定谁跑哪个任务
- arangodb —— ArangoDB — 文档+图+KV 三合一的多模型数据库
- bayou-1995 —— Bayou — 离线先改本地,再回来和别人合并
- berenson-1995-isolation —— Berenson 1995 — ANSI SQL 隔离级别的漏洞与快照隔离
- besu —— Hyperledger Besu — 用 Java 写的以太坊客户端
- bitcoin —— Bitcoin 白皮书
- bitcoin-core —— Bitcoin Core — 比特币参考实现
- borg —— Borg — Google 把一万台机器假装成一台
- borg-omega-kube-2016 —— Borg / Omega / Kubernetes — Google 调度器三代同源
- byzantine-generals-1982 —— 拜占庭将军问题 — 节点能撒谎时怎么达成一致
- calvin-2012 —— Calvin 2012 — 先排好顺序再执行,让跨分区事务不再走 2PC
- capnproto —— Capn Proto — 数据布局即 wire format 的零拷贝序列化 + RPC
- chain-replication-2004 —— Chain Replication — 把多副本排成流水线,简单且强一致
- chandy-lamport-1985 —— Chandy-Lamport 1985 — 分布式系统不停机也能拍一张全家福
- chord-2001 —— Chord — 让上万台机器排成圈,查任何 key 都只走 log N 步
- chubby —— Chubby — 给凡人用的分布式锁服务
- cockroachdb —— CockroachDB — 分布式 SQL 数据库
- cockroachdb-2020 —— CockroachDB 2020 — 没原子钟也能做全球强一致 SQL 数据库
- craq-2009 —— CRAQ — 让链复制每个节点都能读,吞吐线性扩展
- crdt-json —— CRDT JSON — 协同编辑 JSON 数据结构
- crdt-json-2017 —— CRDT JSON 2017 — 给嵌套 JSON 一套有数学证明的合并算法
- crdt-shapiro-2011 —— CRDT — 让多副本各改各的,最终自动合一
- etcd —— etcd — 分布式键值数据库
- fast-paxos-2006 —— Fast Paxos — 给 Paxos 加一条乐观快车道
- fidge-1988 —— Fidge 1988 — 给每个进程一份”账本向量”,让因果关系变成可判定
- filecoin —— Filecoin / Lotus — IPFS 之上的去中心化存储市场
- flexible-paxos-2016 —— Flexible Paxos — 两阶段不一定都要多数派
- flp-1985 —— FLP 1985 — 一个坏节点就能让异步共识永不终止
- go-ethereum —— Go-Ethereum (Geth) — 以太坊主流 Go 客户端
- gray-1981-transaction —— Gray 1981 — 把”事务”提升为通用抽象
- http-2 —— HTTP/2 — 把 HTTP 从文本协议改造成二进制多路复用
- ironfleet-2015 —— IronFleet — 把分布式协议证到一行 bug 都没有
- janus-2016 —— Janus 2016 — 把并发控制和共识捏成一个协议
- kafka-2011 —— Kafka NetDB 2011 — 把消息中间件砍成”会写文件的水管”
- lamport-1978 —— Lamport 1978 — 分布式系统里没有”绝对的同时”
- linearizability-1990 —— Linearizability 1990 — 让并发对象看起来像一次只执行一个操作
- mattern-1989 —— Mattern 1989 — 虚拟时间与全局状态:把分布式时钟变成 N 维笛卡尔积
- mencius-2008 —— Mencius — 让多台服务器轮流当 Paxos 的 leader
- moesi-cache-coherence-1986 —— Sweazey-Smith MOESI 1986 — 给多核 CPU 一份”谁手里有这块内存”的统一规则
- nomad —— Nomad — HashiCorp 出的”轻量版 K8s”工作负载调度器
- omega-2013 —— Omega 2013 — 让多个调度器同时改一份 cluster 状态
- orleans —— Orleans — 让分布式服务写起来像单机对象
- paxos —— Paxos — 分布式共识算法
- paxos-1998 —— Paxos 1998 — 古希腊议会寓言里藏的共识协议
- paxos-simple-2001 —— Paxos Made Simple — Lamport 用平直英语把共识协议推导一遍
- pbft-1999 —— PBFT — 让拜占庭容错从理论变成能跑的工程
- quic —— QUIC — 把可靠传输从内核搬到用户空间
- ravendb —— RavenDB — .NET 生态首选的 ACID 文档数据库
- rethinkdb —— RethinkDB — 让数据库自己把更新推给客户端的先驱
- skeen-3pc-1981 —— Skeen 1981 三阶段提交 — 给 2PC 的阻塞缺陷打补丁
- smr-1990 —— SMR 1990 — 把”容错服务”还原成”多副本一起跑同一台状态机”
- solana —— Solana — Rust 写的高性能 PoH 链
- spanner —— Spanner — 全球分布式 SQL 数据库
- system-r-1976 —— System R 1976 — 第一个跑起来的关系数据库
- tcp —— TCP — 在不可靠的 IP 上凿出一条 reliable 字节流
- tidb —— TiDB — HTAP 分布式数据库
- tidb-2020 —— TiDB 2020 — 给 Raft 加一个”旁听生”,让一份数据同时跑事务和分析
- tigerbeetle —— TigerBeetle — 只能记账但把记账做到极致的金融数据库
- vault —— Vault — HashiCorp 把”密码本”做成可编程基础设施
- verdi-2015 —— Verdi — 在 Coq 里完整证明 Raft 协议的分布式系统验证框架
- vr-1988 —— VR 1988 — 用”主备 + 换届”做共识的另一脉
- vr-revisited-2012 —— VR Revisited 2012 — VR 协议的”工程化重写版”
- zab-2011 —— Zab — ZooKeeper 怎么把客户端写入按顺序复制到所有副本
- zookeeper —— Apache ZooKeeper — 给一群机器装一个共同的小脑