IT 论文精读 · PAPER 50
Diego Ongaro & John Ousterhout · Stanford · USENIX ATC 2014
2014 年,斯坦福的两位研究者(Diego Ongaro 与 John Ousterhout)提出了 Raft——一套让一群计算机「就同一件事达成一致、而且永不反悔」的规则。你天天在用的很多大系统(比如 Kubernetes、数据库 CockroachDB、服务注册中心 etcd / Consul)背后,都有一小群机器在偷偷互相备份、彼此盯着,保证「就算死掉几台,剩下的也记得一模一样的账、绝不打架」。Raft 就是这套「达成一致」的规则。它最特别的地方不是更快或更强——而是故意被设计得让人能读懂、能照着写对。
想象一个小组,每人手里一本一模一样的记事本,上面按顺序记着「做过哪些操作」。规矩是:所有人的本子必须永远逐行相同;一旦某一行被「敲定」,就再也不能改、不能丢——哪怕有人打瞌睡、传纸条中途弄丢了也不行。让一群会犯困、通信还不靠谱的成员,把本子记得分毫不差,这件事有个正经名字,叫共识(consensus)。
共识早就有过标准答案,叫 Paxos,1990 年提出、作者还拿了图灵奖。问题是:它出了名地难懂。教科书讲不清、工程师读不透,真要照着做一个能用的系统,几乎人人都得往里加一堆自己发明、没被验证过的补丁。一个「错一点就出大事」的算法,却几乎没人真正搞懂——这本身就是重大隐患。Raft 的作者干脆把「让人能懂」当成头号设计目标,其它都往后排。
三招,都很朴素。
一是只选一个队长(leader):任何时候只有队长一人能往本子里写新行,其他人只管照抄队长的本子。信息永远从队长流向组员——不像旧办法人人都能提议、乱成一锅粥。
二是用随机闹钟选队长:每人手里一个倒计时长短随机的小闹钟;只要一阵子没听到队长的动静,谁的闹钟先响,谁就站起来喊「选我当队长?」。因为时长随机,两人同时抢的情况很少;万一抢平了,就各自再随机一次,很快能选出来。
三是过半数才算数:队长写下一行后,要等超过一半的人都抄好了,这行才算「敲定」。妙处在于——任意两拨「过半数」的人一定有重叠,所以下一任队长手里必然带着全部敲定过的行,敲定的内容永远不会丢。再补一条:只有本子记得够新的人才有资格当选,这就堵死了「本子缺页的人当上队长、把大家带偏」的漏洞。
Raft 把「共识」从少数专家才敢碰的黑魔法,变成了普通工程师读一篇论文就能实现的东西。今天数不清的基础设施——etcd、Consul、TiKV、CockroachDB、Kafka 的新版协调层——都跑在 Raft 或它的变体上。可以说,它让「可靠的分布式系统」变得人人写得出。
诚实一句:所有写操作都得过队长一个人的手,所以队长是吞吐的天花板;而且队长一掉线,全组要停一小会儿重新选举才能继续。它也只防「宕机」不防「说谎」——假设成员里没人使坏。
选一个队长专门记账、大家照抄,用随机闹钟选队长、用「过半数抄好才算数」保证敲定的内容永不丢失——Raft 把三十年没人读懂的共识算法,重写成一篇能读懂、能照着写对的规则,成了当代无数分布式系统的一致性内核。
想看角色状态机、复制日志图和实验数字? → 切到精读版
Raft 是一套多副本共识(consensus)算法:让一组会宕机、只能通过不可靠网络通信的服务器,对「一串命令的先后顺序」(一份复制日志)达成永不反悔的一致。它的容错能力与性能和 Paxos 相当,但把可理解性当成首要设计目标——靠「强领导者 + 随机化选举 + 把问题拆成领导选举 / 日志复制 / 安全性三块」,让共识第一次变得能教、能懂、能照着写对。如今它是 etcd、Consul、TiKV、CockroachDB 等无数系统的共识内核。
RequestVote(拉票)与 AppendEntries(复制日志 / 心跳)。作者是 Diego Ongaro 与 John Ousterhout,来自斯坦福大学,论文发表于 USENIX ATC 2014(更完整的版本是 Ongaro 的博士论文)。它上承 Lamport 的 Paxos(2001 简化版)与 Multi-Paxos——统治共识领域三十年的经典;下启工业界几乎所有新一代协调系统。与其说 Raft 发明了新原理,不如说它把已有原理重新组织成一个人脑能装得下的形状。
很多关键系统的可靠性,最后都归结到同一件事上:让多台机器就「发生过哪些操作、按什么顺序」达成一致。做法是复制状态机——每台机器存一份相同的命令日志,逐条按序执行,状态自然处处相同;坏掉几台,只要多数还活着,服务照常。Google 的 Chubby、雅虎的 ZooKeeper、GFS 选主,底下都是这样一台「共识引擎」在撑着。
而共识的事实标准,长期是 Lamport 的 Paxos。它正确、经典,却带着两个让工程界头疼多年的毛病。
第一,太难懂。作者直言不讳:Paxos 出了名地晦涩,少有人不下大功夫就能读通——他们自己也是啃了很久、参考了若干二次解读才敢说搞明白。一个正确性攸关的算法,绝大多数使用者却理解得半懂不懂,这是危险的。
第二,难落地。Paxos 原始描述的是「就一个值达成一致」(单决议),可真实系统要的是就一长串值(一份日志)连续达成一致。从单决议拼成 Multi-Paxos 的那部分,论文讲得含糊,于是每个实现都在自行发挥、各加各的补丁,做出来的系统彼此不同、也难证明正确。用 Chubby 团队的话说,实际系统与教科书里的 Paxos 相去甚远。
Ongaro 与 Ousterhout 于是换了个出发点:把「可理解性」当成第一目标去设计一个新算法——凡是能让人更容易学懂、更不容易写错的取舍,就优先采纳。手段有二:一是问题分解(把共识切成几块相对独立、可分别讲清的子问题);二是压缩状态空间(尽量减少系统可能处于的状态、减少「不确定性」,让读者要考虑的情况更少)。Raft 就是这么长出来的。
Raft 的第一个设计决定,是把共识拆成三个能分开理解的子问题:① 领导选举(怎样选出唯一的领导者、原领导挂了怎么补选)、② 日志复制(领导者如何把客户端命令安全地铺到所有机器上)、③ 安全性(用什么规则保证「谁都不会执行到互相矛盾的命令」)。三块各讲各的,合起来就是完整的 Raft。
贯穿三块的核心决定,是强领导者(strong leader):任一时刻集群里最多一个领导者,日志只从领导者单向流向跟随者。这一刀砍掉了 Paxos 里「人人可提议、冲突要协调」的大量复杂度——普通时候,客户端只跟领导者打交道,其余机器被动照抄。
Raft 把时间切成一段段编号递增的任期(term),每个任期以一次选举开始。任期号是全局的逻辑时钟:每条消息都带上发送者的任期,谁看到比自己大的任期,就立刻承认自己过期、退回跟随者并更新任期;谁收到比自己小的任期,就直接拒绝。这一条规则,让「过期的领导者」自动失效,省去大量特判。
服务器只在三种角色间转换:跟随者(follower)被动响应领导者与候选人的 RPC;候选人(candidate)正在竞选;领导者(leader)负责处理所有客户端请求。领导者定期向所有人发空的 AppendEntries 作心跳,宣示「我还在」。跟随者若在一个选举超时内没听到任何心跳,就认为领导者挂了:它把任期号加一、变成候选人、先投自己一票,再向所有人发 RequestVote 拉票。拿到多数票即当选,随即开始发心跳压住场面。
选举的麻烦在于选票分裂:几个跟随者同时超时、同时竞选,票被瓜分,谁都不过半,只能超时重来,可能反复卡住。Raft 的解法简单得漂亮——选举超时时间随机取(如在 150–300 毫秒间随机)。这样大概率只有一个人最先超时、抢先拉到多数票;万一撞车,各自的下次超时又是新的随机值,很快错开。一个随机数,就把「选票分裂」这个老大难压成了小概率、且能快速自愈。
客户端命令只发给领导者。领导者把命令作为新条目追加到自己日志末尾(每条含命令、任期号、索引),然后并行地用 AppendEntries 把它复制给所有跟随者。当这条目被多数派存下,领导者就把它标记为「已提交(committed)」,执行它、把结果回给客户端,并在后续心跳里捎带告诉大家「已经提交到哪儿了」,跟随者随之执行。
怎么保证大家日志真的对齐?靠一条日志匹配性质:若两份日志里某条目的「索引 + 任期」相同,则它俩从头到这一条为止完全一致。实现只用一个小技巧——每次 AppendEntries 都带上「前一条的索引与任期」,跟随者若发现自己那一格对不上,就拒绝。领导者收到拒绝,就把要发的位置往前挪一格重试,直到找到双方一致的分叉点,再用自己的条目覆盖跟随者从那里往后的所有内容。于是领导者从不修改自己的日志,只是强行把跟随者「掰」得和自己一样。
光有上面这些还不够:万一一个缺了已提交条目的跟随者当上新领导者,它一覆盖,就可能把已经提交、已经告诉过客户端的内容抹掉——这是绝不能发生的。Raft 用一条选举限制堵死它:投票时,候选人要在 RequestVote 里带上自己最后一条日志的「任期 + 索引」;只有当候选人的日志「至少和自己一样新」,跟随者才投它(比较规则:先看最后条目的任期,任期大的更新;任期相同则日志更长的更新)。
把这条和「多数派必相交」拼起来,就得到 Raft 最关键的保证——领导者完整性:一条已提交的条目,必然出现在此后每一任领导者的日志里。道理是:条目已提交 = 已在某个多数派上;新领导者当选 = 拿到另一个多数派的票;两个多数派必有交集,交集里那台机器既有这条已提交条目、又给新领导者投了票——而它只会投给日志「不比自己旧」的人,所以新领导者也一定有这条。已提交的东西,就此永不丢失。
还有一处著名的微妙点(论文的 Figure 8):领导者不能仅凭「一条旧任期的条目已被多数派复制」就判定它已提交——因为后来的领导者仍可能把它覆盖掉。Raft 的规矩是:领导者只通过复制自己当前任期的新条目来推进提交;一旦当前任期的条目被提交,靠日志匹配性质,它前面那些旧任期条目也就顺带被间接提交了。这条反直觉的细则,是正确实现 Raft 时最容易踩坑的地方。
此外,Raft 还给出了两块工程必需件:集群成员变更用「联合共识(joint consensus)」两阶段过渡,保证切换配置时不会冒出两个互不重叠的多数派、选出两个领导者;日志压缩用快照,各服务器独立地把已提交的旧日志存成快照、丢掉,避免日志无限膨胀。
Raft 的核心主张是「更好懂」,所以论文罕见地把「可理解性」当指标来量:他们在斯坦福和伯克利找了 43 名学生,各看一段 Paxos 和一段 Raft 的讲解视频、再做配套测验。结果 Raft 的测验平均分比 Paxos 高约 4.9 分(满分 60),43 人里有 33 人 Raft 得分更高;问卷中,大多数人认为 Raft 更容易实现、也更容易向别人讲清楚。
正确性上,作者给出了 Raft 的形式化规范并证明了其安全性(核心是上面的领导者完整性)。性能上,他们实测领导选举:采用随机化超时后,集群在多数配置下能在不到一秒、常在数百毫秒内选出新领导者;而一旦缩小随机范围,选票分裂就让选举时间显著拉长——反过来印证了「随机超时」这一招的价值。至于日常吞吐与延迟,Raft 与 Multi-Paxos 属于同一量级。
Raft 的影响力,更多在工程与教育而非理论突破。论文发表后很快涌现出数十个开源实现,并迅速成为工业界事实标准:Kubernetes 的元数据存储 etcd、HashiCorp 的 Consul、TiDB 的 TiKV、CockroachDB、RethinkDB,乃至 Kafka 用来取代 ZooKeeper 的 KRaft,共识内核都是 Raft 或其变体。它把「搭一个强一致的分布式系统」的门槛,从「必须是共识专家」降到「读懂一篇论文」。
更深一层,Raft 证明了「可理解性」本身可以是一流的研究目标:同样的容错保证,换一种讲法、换一套抽象,就能让整个行业受益。它也顺势成了几乎所有分布式系统课程里讲共识的首选教材。
① 一句话:Raft 是一套多副本共识算法,让一组会宕机的机器就「一份复制日志」达成永不反悔的一致;核心卖点是为可理解性而设计。
② 痛点:Paxos 正确却极难懂、且从单决议到「日志」的落地含糊,人人自行发挥、难保正确——一个正确性攸关的算法几乎没人真读通。
③ 分解:把共识拆成领导选举 / 日志复制 / 安全性三块分别讲清;贯穿全局的是强领导者——日志只从领导者单向流向跟随者。
④ 选举:用任期做逻辑时钟、见更高任期即退位;用随机化选举超时把「选票分裂」压成小概率并快速自愈;拿多数票即当选。
⑤ 复制:命令经领导者追加、AppendEntries 铺给众人,被多数派复制即提交;靠「前一条索引+任期」的一致性检查强行对齐跟随者日志。
⑥ 安全:只有日志「够新」的候选人能当选(选举限制)+ 多数派必相交 ⇒ 领导者完整性:已提交条目必存于此后每任领导者,永不丢失。
⑦ 微妙点:领导者只靠复制本任期新条目来推进提交,旧任期条目间接被提交(Figure 8)——最易实现错的一处。
⑧ 结果与影响:可理解性用户研究里显著胜过 Paxos;催生 etcd、Consul、TiKV、CockroachDB、KRaft 等一众系统,成为共识的事实标准与教学首选。
⑨ 局限:单领导者瓶颈 + 选举不可用窗口;容错 / 性能不超 Paxos;不防拜占庭;成员变更曾被发现暗坑;超时需调参。