棋盘镇

棋盘镇不大,从南到北五条街,从东到西五条巷,整整齐齐像个棋盘。

镇上有五位理事:张伯、王叔、李婶、赵哥、陈嫂。他们一起管着镇上大小事务——修桥铺路、税收开支、节庆安排,全由这五个人商量着定。

镇上有一本公账,封面是牛皮,内页是桑皮纸。所有镇上做过的事、花过钱的地方,都一笔一笔记在这本公账上。

账本只有一本,锁在议事厅的铁柜里,钥匙由镇长沈公掌管。

沈公管了棋盘镇二十年。每逢有事,他去铁柜取出账本,翻开最末一页,写下决定,然后拿给五位理事看。看完了,大家点点头,事就定了。

简单得很。二十年没出过一桩错。

直到那年秋天,沈公病倒了。


出事

沈公一病就是两个月。起初大家以为他只是风寒,过几天就好。

但账不能等人。

先是桥头塌了一段,赵哥说修桥要五十两。李婶记下,拿去给沈公签字——沈公迷迷糊糊,挥了挥手就睡过去了。

李婶把账本放回铁柜。但王叔正好路过桥头,觉得工程不小,得花八十两。他自己去铁柜拿了账本,也记了一笔。

张伯不知道他俩都记了,觉得修桥应该是六十五两,直接翻到空白页,写上自己的那一笔。

到了月底,陈嫂翻开账本一看:同一座桥,修了三遍,三个不同的价钱。更糟的是,有一笔账记在了另一笔的后面,谁也说不清哪笔在先、哪笔在后。账本的前后顺序全乱了。

"这怎么行?"陈嫂召集大家开会,"账本上到底哪一笔是真的?"

"我记的!"赵哥说。

"我也记了,"王叔说,"但我不知道你也记了。"

"我更不知道你们两个都记了。"张伯叹气。

李婶问:"那咱们之前有没有同意修桥这件事?"

四个人回忆了半天,说不清。

最后桥还是修了——但因为账目不清,修桥的银匠跑了两趟,材料多买了一次,事情办得乱七八糟。


路过的书生

这天镇上来了个书生,姓许,自称在户部做过十年书吏。他路过棋盘镇,正好撞见五位理事在议事厅里吵架。

"桥的事还没扯清楚,现在又来了灯会的开销!"赵哥拍着桌子。

"我说灯会花三十两,你偏说四十两——账本上到底写了多少?"王叔指着张伯。

"你们别吵,"张伯说,"我把灯会记在第十三页——"

"第十三页是我记的桥!"李婶打断他。

许书生在门口站了一会儿,听明白了。他笑了。

"各位理事,"他拱了拱手,"你们这个问题,我在户部的时候见过。"

"户部?"陈嫂眼睛一亮,"户部怎么解决的?"

"户部管着天下钱粮,账房有几十号人,各地呈上来的账册堆成山,"许书生说,"要是人人都能在同一本账上随意写画,不出三天就全乱了——比你们这桥的事情严重一百倍。"

他从怀里掏出一本旧册子:"户部定过一套规矩,叫'议账法'。你们想听吗?"

五个人对视一眼,同时点了点头。


议事法

许书生在桌上铺开一张白纸,先写了第一条规矩:

"只会有一个人在记账——选一个主簿,只有他能动账本。"

"这不就是沈公吗?"赵哥说,"可他现在病着。"

"对,"许书生说,"第一个问题就是:主簿不能是固定的一个人。人总会病,总会老。你们需要——选主簿。"

他继续写第二条:

"主簿是选出来的,任期固定。任期到了,重新选。任期中间主簿失联了,马上选新的。"

"怎么选?"陈嫂问。

"投票,"许书生说,"五个理事,谁拿到三票以上,谁就当主簿。如果没人能拿到三票——"

"那怎么办?"王叔问。

"那就等一会儿,重新投。"许书生说,"等多久?这个很关键——你们每个人等的时间不能一样。张伯你等一刻钟,王叔等两刻钟,李婶等三刻钟……总之要错开,否则每次投票都会撞在一起。"

赵哥皱起眉:"为什么每个人等的时间不一样就不撞车?"

"你想,"许书生说,"如果五个人都等同样长的时间——比如都等一刻钟——那么时间一到,五个人同时站起来说'我要求重选',又是谁也不够三票。但如果等的时间不一样呢?李婶等三刻钟,她还没等到时间,张伯的一刻钟就已经到了——张伯先站起来说'我要当主簿',其他人这时候还没动,他就能拿到票。"

"明白了,"陈嫂说,"像排队一样,总得有人先站出来。"


心跳

许书生继续往下写第三条规矩:

"主簿必须定期报平安。如果他沉默了太久,其他人就当他失联了。"

"定期报平安?"赵哥问,"怎么报?"

"很简单,"许书生说,"主簿每隔一会儿——比如半刻钟——就往议事厅里喊一声:'一切照旧,无需新事'。这句话的意思是:我还活着,账本在我这儿,没什么新事要记。"

"那不是废话吗?"王叔说。

"看着是废话,"许书生笑了,"但这句话是整个规矩里最关键的一条。你想,如果主簿突然不喊了,你们会怎么想?"

李婶一拍大腿:"那他就是出事了!病了、走了、或者忘了——不管是哪种,他都不该继续当主簿了。"

"正是,"许书生说,"这时候,听到沉默的人开始等。等的时间到了,就站起来说:'旧主簿失联了,我要求选新的——这一任的任期号多加一。'"

他补充了一句:"任期号永远只增不减。现在是第一任,下一任是第二任,再下一任是第三任……不管是选新主簿,还是旧主簿病好了想回来接着干——任期号不够的,没人理他。"

"为什么?"陈嫂问。

"因为主簿可能不是真病了,"许书生说,"也许他只是打了个盹。等他醒来,你们已经选好了新主簿。他来一句:'我还没病呢,我还是主簿!'——让他回来吗?"

"那不行,"张伯说,"已经选了新的了。"

"所以任期号要加一,"许书生说,"旧主簿任期号太低,说话没人听。这叫——只有最新的任期说了算,过期的任期靠边站。"


记账

规矩说到这儿,五位理事已经明白怎么选主簿了。但还有一个问题:账本到底怎么记?

"选出了主簿,主簿拿到了账本,"许书生翻到册子的下一页,"接下来是这样——"

他在纸上画了一个图:

  1. 有人提议:任何理事想记一笔账,把内容告诉主簿。
  2. 主簿起草:主簿在账本最后一页之后,写上这笔账——但此时用的是铅笔,还没用墨。
  3. 主簿问询:主簿拿着账本走到每个理事面前,问:"这一页、这一行、这个内容——你认不认?"
  4. 理事点头:理事看了,说"认"。
  5. 凑够三票:主簿拿到三个"认"之后——包括自己——就用墨把铅笔字描成毛笔字。这笔账就定了,再也改不了。
  6. 主簿告之:主簿对所有人说"第几页第几笔已定",大家各自在自己的副本上抄一遍。

"等等,"李婶打断,"为什么不能写好了直接就定?非得要三步——起草、问询、定稿?"

"因为主簿会出事,"许书生说,"如果主簿起草了一笔账,还没问别人,他突然病倒了——那新主簿该怎么办?继续用这笔铅笔字,还是擦掉重来?"

"那……"赵哥想了想,"应该擦掉重来吧?毕竟就他一个人写的。"

"对,"许书生说,"所以铅笔和墨的区别很重要——只有过了半数的账才算数。没过半数的,不管是铅笔写的还是前任主簿留下的,一律不算。"

他顿了顿,又补了一句:"还有一点:新主簿上任之后,第一件事不是记新账,而是先把自己的账本和前主簿的对一遍。如果新主簿的账本短了一截——说明他漏看了几笔——先补齐,再开始记新的。"

"这是为了防止什么?"张伯问。

"防止新主簿一上来就急急忙忙往账本里填自己的东西,结果把前主簿还没定稿的铅笔字全盖了。"


考验

规矩定好之后,五位理事决定试试。

第一任主簿选了赵哥。他上任之后,每过半刻钟就喊一声"一切照旧"。

第一天顺顺当当。赵哥记了三笔账——修桥的尾款、灯会的预算、镇学先生的月钱——每一笔都问了人,凑够了三票,用墨描实了。

第二天,赵哥照常喊"一切照旧"。喊到下午,他突然不喊了。

大家又等了半刻钟——还是没声音。

"赵哥怎么了?"王叔放下茶碗。

张伯第一个起身——他的等待时间最短,只有一刻钟。他走到议事厅中间,清了清嗓子说:"赵哥失联了。这是第二任期,我要竞选主簿。谁支持我?"

李婶问:"你能证明你是最新的任期吗?"

"当然,"张伯说,"我来写任期号和我的名字,大家都看见。"

陈嫂举手:"我支持。"

王叔犹豫了一下:"我也支持。赵哥确实没动静了。"

三票。张伯成了第二任主簿。

他做的第一件事,不是记新账——他翻开账本,找到赵哥最后定稿的那一页,把赵哥还没用墨描实的铅笔字一笔一笔问了一遍。

"这笔——赵哥在第几任时写的?谁认过?"

有一笔只有赵哥自己认过,不够数,擦了;有一笔赵哥问过三个人,够数了,补上墨。

补完之后,张伯才开始记新账。

一个时辰之后,赵哥回来了——原来他只是去了趟茅房,不小心在草垛上睡着了。他揉着眼睛走进议事厅:"我回来了,咱们继续记——"

"等等,"李婶拦住了他,"你的任期已经结束了。现在是张伯的第二任期。"

赵哥愣了一下。他想起许书生的规矩,笑了起来:"好吧。我的任期号只有一,张伯是二。我说话不作数了。"

他坐下来,变成了普通理事。张伯继续当主簿,账本一页一页地往下记,再也没有乱过。


门道

许书生临走之前,五位理事请他吃饭。

席间,王叔忍不住问:"许先生,你这套规矩看着简单,我们一天就学会了。但我想不通——为什么它就这么稳?我们之前吵了两个月都理不清的事,现在三天没出一桩岔子。"

许书生举起酒杯,隔空对着月亮,慢慢说了三句话:

"第一,账本只有一个,动笔的手也只许有一只。 两只手一起动笔,账本必乱。人也好、机器也好,并行写同一本账,是万乱之源。"

"第二,任何一笔账,没拿到三票就不算数。 一个人说了不算,两个人也说了不算——必须超过半数。这条规矩让所有定了的账都不可能再被推翻。"

"第三,任期永远往上走,不回头。 任期号大的说话有人听,任期号小的说话没人理。一个过期的主簿永远不可能回来搅局。"

陈嫂放下筷子,沉默了一会儿。她突然问:"那沈公当年怎么就没出事呢?"

许书生笑了:"沈公没出事,是因为他二十年没有中断过。你们一个人、一本账、不断档——不出事。可一旦人多了,事情杂了,中断避免不了,规矩就必须跟上。可靠不是不断电,而是断了电之后还能接上。"

他看着窗外的棋盘镇,月光漏进巷子,照出一条条笔直的线:

"你们记住——把一堆容易出错的人凑在一起,想让他们做出一件不出错的事,唯一的办法就是让他们按规矩协作,而不是比谁更聪明。"


谜底:这个故事到底在讲什么?

这个故事完整描述了分布式系统中最重要的协议之一——Raft共识算法(Raft Consensus Algorithm)。

在分布式系统中,多台服务器需要就"数据的状态"达成一致。比如一个数据库集群,每个节点都存一份数据副本——当客户端写入一条新数据时,如何保证所有节点都同意这条数据应该被写入、并且写入的顺序一致?这就是共识算法要解决的问题。

Raft 是由 Diego Ongaro 和 John Ousterhout 在 2014 年提出的共识算法,它的设计目标就是"好理解"——相比之前的 Paxos 算法,Raft 把共识问题拆成了三个独立的子问题:Leader Election(领导选举)Log Replication(日志复制)Safety(安全性)

核心概念回顾

概念 通俗解释
共识算法(Consensus Algorithm) 让多台服务器对一个"值"或"操作序列"达成一致的规则
Leader(领导者) 唯一有权处理写请求的节点,负责把日志条目复制到其他节点
Follower(跟随者) 被动接收 Leader 的日志,不主动发起写请求
Candidate(候选者) 发起选举的节点,介于 Follower 和 Leader 之间的临时状态
Term(任期) 单调递增的整数,每一轮选举对应一个新任期
Log Entry(日志条目) 一条操作记录,包含数据、任期号和日志位置索引
Commit(提交) 一条日志被多数节点确认后,标记为"已提交",可以被安全地应用到状态机
Heartbeat(心跳) Leader 定期发送的空消息,告诉其他节点"我还活着"
Quorum(法定人数) 超过半数(如 5 个节点对应 3 票),是 Raft 一致性的数学基础
Election Timeout(选举超时) Follower 在没收到心跳后等待的时间,超时后发起选举
Split Vote(分裂投票) 多个 Candidate 同时发起选举,没人拿到多数票——用随机超时避免
Log Matching Property(日志匹配性质) 如果两条日志有相同的索引和任期号,那么它们之前的所有日志都相同

故事中的隐喻对照

故事元素 映射的 Raft 概念 解释
五位理事 5 节点集群(5-node Cluster) 分布式系统中的 5 台服务器,可以容忍最多 2 台故障(5 = 2×2+1)
牛皮账本 复制日志(Replicated Log) Raft 的核心数据结构:一个有序的日志条目序列,每个节点都有一份
沈公 单点 Leader(Single Leader) 旧系统依赖一个人——这就是分布式系统中要避免的"单点故障"
主簿(轮值) Leader(领导者) 任期内唯一有权追加日志的节点
任期号 Term Number 单调递增的整数,Raft 用 Term 来检测过期信息——Term 低的节点的消息会被忽略
投票选主簿(拿三票当选) Leader Election + Quorum Candidate 需要拿到多数节点的选票(Follower 在一个 Term 内只能投一票)才能成为 Leader
"一切照旧,无需新事" Heartbeat / AppendEntries RPC Leader 周期性发送心跳,Follower 用它重置选举超时时钟
等待的时间各不同 Randomized Election Timeout 每个节点随机等待(如 150-300ms),确保只有一个节点先超时并发起选举,避免分裂投票
铅笔 → 问人 → 墨描 Log Entry → Replicate → Commit Leader 先追加条目到本地日志(铅笔),通过 AppendEntries 复制到 Follower(问人),收到多数确认后标记为 Committed(墨描)
不够三票的铅笔字擦了 未提交日志回滚(Uncommitted Log Rollback) 只有被多数节点确认的日志才能被提交。Leader 变更后,前任未提交的日志可能被新 Leader 覆盖
新主簿先补齐前任的账 Leader Completeness + Log Matching 新 Leader 不会删除或覆盖前任已提交的日志;它先通过 AppendEntries 确保所有 Follower 日志和自己一致
赵哥在草垛上睡着了 Leader 故障(Leader Failure) 分布式系统中节点可能因网络分区、进程崩溃等"失联"
赵哥回来后任期号不够,说话没人听 Term Check(任期检查) 旧 Leader 恢复后发现自己的 Term 过期,收到更高 Term 的消息后自动降级为 Follower
任期号永远只增不减 Term 单调递增 所有节点只响应 Term ≥ currentTerm 的请求;看到更高的 Term 就更新自己的 currentTerm
许书生的三条总结 Raft 的三条核心保证 ① Leader 唯一性(Election Safety)② 多数确认才提交(Commit Safety)③ Term 单调递增(Term Safety)

为什么这个故事对应 Raft?

让我们复盘故事中的关键机制:

  1. 为什么必须选主簿,不能五个人一起记账? → 对应 Raft 的 强领导模型(Strong Leader)。Raft 规定所有日志只能从 Leader 流向 Follower——Follower 从不主动写。这保证了日志的单一来源,避免了并发写的冲突。相比之下,Paxos 允许多个节点提议,复杂度高得多。Raft 的设计理念就是"好理解胜过好聪明"。

  2. 为什么是"过半"(三票)而不是全部? → 对应 Quorum(法定人数)机制。在 N 个节点中,只要超过 N/2 个节点确认,操作就成立。这有两个好处:一是允许少数节点故障(5 个节点可以容忍 2 个故障);二是任何两个多数派必然有交集——这保证了"一旦提交,永不被覆盖"的安全性。

  3. 为什么新主簿必须先对齐账本再记新账? → 对应 Raft 的 Leader Completeness Property。新 Leader 在开始服务写请求之前,必须先通过 AppendEntries 将 Follower 的日志同步到和自己的日志一致。这保证了新 Leader 拥有所有已提交的日志条目。

  4. 为什么每个人等待的时间不一样? → 这是 Raft 的一个精巧设计。如果超时时间相同,多个节点会同时超时、同时发起选举,导致 分裂投票(Split Vote)——每轮都没人拿到多数票。随机化超时让只有一个节点先发起选举的概率大幅提高。

  5. 为什么赵哥说"我的任期号只有一,我说话不作数"? → 对应 Raft 的 Term 检查机制。每个 RPC 消息都携带 Term 号。接收方如果发现发送方的 Term 比自己低,就拒绝这个请求。这让旧 Leader 自动失去权威,不需要显式"退位"。

  6. 为什么许书生说沈公二十年不出错是因为"没中断"? → 这是分布式系统的核心洞察:单点系统在最简单的情况下不出错,因为根本不存在一致性问题。 一致性问题的本质是"多点 + 故障",解决之道不是让故障不发生,而是让系统在故障发生时仍能正确运转。


Raft 的三个子问题与状态转移

故事中隐含了 Raft 的三个核心子问题:

子问题 故事中的对应 Raft 机制 关键保证
Leader Election(领导选举) 投票选主簿、等待时间各不相同 Candidate 发起 RequestVote,Follower 按 Term 投票,随机超时避免分裂 同一任期最多一个 Leader(Election Safety)
Log Replication(日志复制) 主簿起草→问询→凑票→墨描 Leader 通过 AppendEntries 发送日志条目,Follower 返回确认 多数确认后提交,最终所有节点日志一致
Safety(安全性) 新主簿先对齐前任账本、任期号只增不减 Leader 不会覆盖已提交日志、Term 单调递增、日志匹配性质 一旦提交永不丢失(Commit Safety)

Raft 通过三个角色状态(Leader、Follower、Candidate)和 Term 概念,把这三个子问题统一在了一个清晰的框架里。


后记:Raft 的设计哲学和很多"天才式"的算法不同——它不求优雅或简洁到极致,而是力求可理解。它的论文里有一句著名的话:"Raft is designed to be easier to understand than Paxos." 在工程实践中,可理解性意味着更容易实现、更不容易出错,也更容易调试。棋盘镇的故事试着用"人的协作"来还原 Raft 的精髓:把共识问题拆分开来,用清晰的规则约束每个人——好的规则不是为了不让故障发生,而是让故障发生之后,账本不乱。