Raft 2014 — 把共识拆成能实现的三件事
待复核Raft 是一种让多台机器像一台机器那样决定同一串命令的共识算法。日常类比:像几个值班同学一起记班级账本,大家不靠记忆争论,而是先选一个班长,由班长按顺序登记,其他人照抄。
这篇论文的重点不只是“又发明一个共识算法”,而是把原来很难讲清楚的 Paxos 拆成更容易理解的三块:leader election、log replication 和 safety。
用工程话说,Raft 解决的是 replicated state machine:客户端发来命令,多个服务器必须以同样顺序执行,哪怕机器宕机、网络丢包、leader 换人,最终也不能执行出两份不同历史。
不理解 Raft,下面这些事都很难解释:
- 为什么 etcd、TiKV、CockroachDB 这类系统可以容忍少数节点挂掉,还能继续对外服务
- 为什么“多数派”不是投票仪式,而是保证两次决策必然有交集的安全机制
- 为什么 leader 不能随便提交旧 term 的日志,否则看起来复制到多数派的记录也可能被覆盖
- 为什么一篇算法论文会把“可理解性”当成一等目标,因为实现者不理解就很难保住正确性
Raft 的核心可以拆成 三件事:
-
Leader election:先选一个临时负责人。类比:群聊讨论容易乱,先定一个主持人;主持人失联,大家等一个随机时间再重新竞选,避免所有人同时抢麦。
-
Log replication:所有命令都走同一本流水账。类比:班长先把“今天买粉笔”写在第 7 行,再让其他同学抄第 7 行;多数人抄到后,这行才算稳。
-
Safety:换班长也不能改掉已确认的账。类比:新班长上任前必须证明自己手里的账本足够新,不能拿着缺页账本来覆盖别人已经确认的记录。
这三件事放在一起,Raft 就把“大家到底同不同意”变成了“谁能当 leader、leader 怎么发日志、哪些日志永远不能丢”。
案例 1:随机超时怎么减少抢 leader
Section titled “案例 1:随机超时怎么减少抢 leader”const timeout = randomBetween(150, 300)if (noHeartbeatFor(timeout)) { term += 1 state = "candidate" requestVotesFromPeers(term)}逐部分解释:
noHeartbeatFor(timeout)表示 follower 一段时间没收到 leader 心跳,就怀疑 leader 挂了randomBetween(150, 300)让每台机器等待时间不同,通常最早醒来的那台先发起选举term += 1像换届编号,后面的消息都带着这个编号,旧编号的 leader 会自动让位
案例 2:leader 复制一条日志
Section titled “案例 2:leader 复制一条日志”log.append({ index: 7, term, command: "set x=1" })sendAppendEntriesToFollowers(log[7])if (replicatedOnMajority(7)) { commitIndex = 7 applyToStateMachine(log[7])}逐部分解释:
index: 7是账本行号,term是这行由哪一届 leader 创建- leader 并行发
AppendEntries,慢 follower 不会挡住多数派提交 applyToStateMachine必须等 committed 之后执行,否则机器可能提前执行一条后来被覆盖的命令
案例 3:投票时检查候选人的日志新不新
Section titled “案例 3:投票时检查候选人的日志新不新”function canVote(candidate, voter) { if (candidate.lastTerm !== voter.lastTerm) { return candidate.lastTerm > voter.lastTerm } return candidate.lastIndex >= voter.lastIndex}逐部分解释:
- 先比最后一条日志的
term,term 大说明候选人见过更新一届 leader 的记录 - term 相同再比
index,index 大说明账本更长 - 这个限制让缺少已提交日志的服务器很难当选,从入口处保护 safety
-
把多数派当成“超过一半就安全”:只有当前 term 的日志能靠多数派直接提交,旧 term 日志要等当前 term 日志提交后间接安全。
-
以为 follower 可以保留自己的冲突日志:Raft 的方向是 leader 覆盖 follower 冲突部分,避免日志双向合并带来额外复杂度。
-
把 election timeout 设得太接近心跳间隔:网络稍微抖一下就会误触发选举,系统可用性会变差。
-
忽略客户端重试的重复命令:leader 崩溃后客户端可能重发请求,实际系统还要用请求编号做去重,论文短版把这部分放到扩展版。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 需要一组节点维护同一份强一致元数据,比如配置、锁、租约、集群成员信息
- 可以接受多数派可用模型:5 台机器挂 2 台还能继续,挂 3 台就必须停下来保护正确性
- 想从工程角度学习 consensus,先抓 leader、log、safety 三条主线
不适用:
- 需要容忍恶意节点撒谎的场景,Raft 只处理 crash fault,不处理拜占庭故障
- 只需要最终一致的缓存或推荐数据,强一致日志反而会增加延迟和复杂度
- 网络长期分区且没有多数派的一边仍想继续写入,这会违反共识的基本安全前提
- 多 leader、跨广域网低延迟写入是第一目标时,需要看 EPaxos、Mencius 或其他变体
历史小故事(可跳过)
Section titled “历史小故事(可跳过)”- 1989 年前后:Paxos 被提出,思想很强,但论文表达和工程细节让很多学生、工程师望而却步。
- 2012 年:Viewstamped Replication Revisited 重新整理 leader-based 共识路线,给 Raft 这类讲法提供了参照物。
- 2013 年:Ongaro 和 Ousterhout 围绕“更容易理解”设计 Raft,并用教学实验和 Paxos 对比。
- 2014 年:Raft 在 USENIX ATC 发表,很快成为课程、项目和开源实现学习 consensus 的标准入口。
- 之后几年:etcd、TiKV、CockroachDB 等系统把 Raft 变成日常基础设施的一部分。
- 共识不是一句“大家同意”,而是把命令顺序、leader 任期、提交规则都钉死。
- 可理解性是工程属性:算法越容易形成直觉,实现者越不容易在边界情况里改坏它。
- 多数派的价值在交集:任意两个多数派至少共享一个节点,这个节点把历史信息带到下一次决策。
- Raft 的保守规则是有意的:例如不直接提交旧 term 日志,牺牲一点灵活性,换来更容易推理的 safety。
- 论文 PDF:In Search of an Understandable Consensus Algorithm
- 会议版本:USENIX ATC 2014 Raft 页面
- 可视化学习:Raft 官方网站
- 形式化方向:lamport-tla-1994 —— 论文也提到用 TLA+ 写过约 400 行规格
- 对照阅读:paxos-simple-2001 —— 看懂 Paxos 后更能体会 Raft 为什么强调可理解性
- 工程入口:etcd —— 现实项目里最常见的 Raft 学习对象之一
- paxos-simple-2001 —— Raft 的叙事目标之一就是降低 Paxos 学习门槛
- paxos-1998 —— 原始 Paxos 是共识算法绕不开的历史起点
- lamport-time-clocks-1978 —— term 和日志顺序都离不开分布式时间观念
- lamport-tla-1994 —— Raft 论文用 TLA+ 规格和证明来支撑 correctness
- zookeeper —— 同样是 leader-based 协调系统,能对照看日志流动方向
- etcd —— 生产环境学习 Raft 的经典工程项目
- tikv —— 多副本存储系统中 Raft 如何落地的现实案例
- epaxos-2013 —— EPaxos — 没有 leader 的 Paxos,让每个副本平起平坐
- hotstuff-2019 —— HotStuff — 让换领导也只花线性消息的 BFT 共识
- tao-2013 —— TAO — Facebook 给十亿人好友列表造的专用图数据库