议事厅的轮值官
一、七个长老,一件事也定不下来
青山镇有个议事厅,厅里坐着七位长老。镇上大小事务——修桥、办学、分粮、招商——都得七位长老一起议、一起定。
长老们都是好人,但不代表他们不吵架。
"南街的桥该修了。"赵长老说。
"修北街的,北街的人多。"钱长老说。
"我两个都不同意,"孙长老说,"今年的银子不够修两座,不如先补学堂的瓦。"
七个人,七张嘴。从辰时吵到酉时,天黑了,什么也没定下来。
更麻烦的是,镇上办事的人来问结果——桥修不修?粮分不分?——长老们你看我我看你,各说各的。办事的人不知道该听谁的,只好回去。
等了一年,南街的桥塌了。学堂的瓦也没补。
二、轮值的主席,过半的票
镇上有个年轻人叫阿序,在京城念过书。回乡看见议事厅这副光景,他找到七位长老。
"叔伯们,你们不是想法不对,是议事的法子不对。"
七位长老一齐看向他。
"我先问一句——你们认不认一个道理:一件事,只要超过一半的人同意,就算定了。"
赵长老说:"那当然。四个同意,就算。"
"好。"阿序从怀里掏出一个铜铃,"那我再问——你们愿不愿意,每次议事的时候,指定一个人来'主持'?这个人摇铃开会、收集提议、主持投票。"
"轮流当?"钱长老问。
"轮流当。"阿序说,"每人当一阵子,叫一'任'。一任之内,所有提议都由他发起。他提出方案,你们投票——过半就算通过。通不过?他改方案再投。"
孙长老皱起眉头:"那要是他提出了毛病的主意呢?"
"任期到了就换人。"阿序说,"而且每一项决议,都要记在议事簿上——第一条、第二条、第三条——按顺序编号,永不翻案。"
七位长老面面相觑。这法子简单,但以前没人提过。
第二天,他们试了。
赵长老摇铃,当第一任主席。他提"修南街的桥",投票:四票赞成,三票反对。过了。记在议事簿第一条。
第二任轮到钱长老。他提"补学堂的瓦",投票:五票赞成,两票反对。过了。记在议事簿第二条。
第三任孙长老。他提"开镇粮仓接济孤寡",投票:六票赞成,一票反对。过了。记在议事簿第三条。
一天定了三件事,比过去一年还多。
三、主席睡着了,新主席顶上
好景不长。有一天轮到郑长老当轮值主席。他年纪大了,在议事厅坐到一半,靠在椅背上睡着了——睡了大半个时辰。镇上来了三拨人请示,全被堵在门外。
阿序看到这一幕,又给规则加了一条。
"每一任主席,每隔一盏茶的功夫,必须摇一下铃。铃声告诉大家:主席还在、还在管事。这铃声叫'心跳'。"
"如果隔了三盏茶,铃还没响呢?"钱长老问。
"那就说明主席不在了——不管他是睡着了、出门了、还是病了。你们六个人立刻推举一位新主席,任期从'下一任'开始。旧主席醒来以后,发现自己不是主席了,就得按普通长老的身份继续议事。"
"那旧主席在职时定下的决议呢?"李长老追问。
"全部保留。"阿序说,"议事簿上的每一条决议,都经过了过半票数,永久有效。新主席上任,第一件事就是把议事簿从头到尾对一遍——自己的簿子和前任的簿子,一条不差。"
"怎么对?"
"前任的簿子上,每一条都有编号、都有票数。少一条?补上。编号对不上?按前任的来。就这么简单——新主席不另起炉灶,只在旧簿子上往下续。"
长老们沉默了一会儿。郑长老开口了:"就是说,主席可以换,但主席答应过的事不能赖。"
"对。"
"这规矩好。"
四、风雨夜,七个人只到了四个
那年秋天,一场大风雨把青山镇的桥冲断了——不光是南街的,北街的也断了。
七位长老连夜议事。这次轮到吴长老当主席。他先摇了铃,然后提第一条:"紧急征调全镇工匠,先搭浮桥。"
投票:在场的四个长老全数赞成。但还不够——七个长老的过半是四票,恰好够。过。记在议事簿第十七条。
第二条:"开镇粮仓,拨三百石粮食救济被困人家。"
投票:三个赞成,一个反对。不过半——风雨太大,另外三位长老没能赶来。
吴长老皱眉:"我们在场四个,三票赞成。能不能算过?"
阿序站在旁边,平静地说:"过半,是全部七个人的过半。三票不够,必须是四票。哪怕三位长老没到,规矩不变。你们没凑够四票,这条就不能记正册——最多记在'候议'栏里,等风停以后重新投。"
吴长老愣住了。片刻后,他点了点头:"宁可少记一条,也不能记一条假的。"
"对。议事簿上的每一条,都是过了全数过半真金白银的票。"
风雨停了。第二天,三位缺席的长老赶到了。吴长老把"拨粮三百石"重新提了一遍:这回七票全过。记入议事簿正册第十八条。
五、人心不是靠嘴拢齐的
又过了大半年。青山镇大小事务定了六十几条,件件有票可查、有号可追。十里八乡都来取经。
有个外乡的长老拉着阿序问:"你们这个轮值主席的法子,到底好在哪里?"
阿序想了想,伸出三根手指:
"第一,任何时候只有一个人主持,不会七嘴八舌。 主席在任期内,所有提议由他发起。别人有想法?告诉主席,主席代为提案。"
"第二,每一条决议都有编号、都有票数,什么时候查都清清楚楚。 谁也翻不了旧账,谁也编不了假票。"
"第三——"他顿了顿,"主席可以换,决议不会翻。哪怕风雨把半个议事厅冲走了,活下来的长老凑够四个,手里的旧簿子一拼,全部记录都在。新主席上任,先补簿子再议事。"
外乡长老沉默了很久。最后他说:"原来拢齐人心,不是靠谁的嗓门大。"
人心不是靠嘴拢齐的,是靠规矩。规矩到了,人心自己就拢了。
技术解读
Raft 是一种分布式共识算法,由 Diego Ongaro 和 John Ousterhout 于 2014 年在 USENIX ATC 发表论文《In Search of an Understandable Consensus Algorithm》中提出。它的设计目标很明确:让共识算法变得"可理解"。在此之前,Paxos(Lamport, 1989)一直是共识算法的标准,但因其难以理解和实现,工业界一直期待一种更直观的替代方案。Raft 通过将共识问题分解为三个相对独立的子问题——领导者选举、日志复制、安全性——成功做到了这一点。如今,etcd、TiKV、Consul 等众多分布式系统都基于 Raft 实现。
核心概念回顾
| 概念 | 通俗解释 |
|---|---|
| 共识算法 | 让多个节点对同一个值(或一系列值)达成一致的协议,即使部分节点故障也能正常工作 |
| Leader(领导者) | 集群中唯一负责接收客户端请求并将其复制到所有节点的角色,Raft 在任何时刻最多有一个有效的 Leader |
| Term(任期) | 时间被划分为连续的任期,每个任期最多有一个 Leader。任期号单调递增,是 Raft 中的逻辑时钟 |
| Heartbeat(心跳) | Leader 周期性向所有 Follower 发送的消息,既用于维持权威,也用于阻止 Follower 发起新的选举 |
| Leader Election(领导者选举) | 当 Follower 在超时时间内未收到心跳,就转为 Candidate,增加任期号并发起投票。获得多数票的 Candidate 成为新 Leader |
| Log Replication(日志复制) | Leader 将客户端请求追加到自己的日志中,然后并行复制给所有 Follower。当日志条目被多数节点确认后,Leader 将其"提交"并应用到状态机 |
| Quorum(多数派 / 过半) | 超过半数节点同意即为通过。N 个节点的集群可以容忍最多 (N-1)/2 个节点故障 |
| Committed(已提交) | 一旦日志条目被多数节点持久化,就被标记为"已提交",保证永远不会被未来 Leader 覆盖 |
| Log Matching Property | 如果两个日志在相同索引和任期号上有一条记录,则它们在该索引之前的所有记录完全一致 |
故事中的隐喻对照
| 故事元素 | 映射的技术概念 | 解释 |
|---|---|---|
| 七位长老 | Raft 集群的 7 个节点 | 典型 Raft 集群使用奇数个节点(通常 3、5、7),以便形成多数派 |
| 议事厅 | 分布式系统中的集群 | 所有节点在一个逻辑集群中协同工作 |
| 轮值主席 | Leader(领导者) | 每个任期只有一个 Leader,负责发起所有提案并协调投票 |
| 任期 / 轮值 | Term(任期号) | 时间被划分为连续任期,每个任期最多一个 Leader。任期号单调递增 |
| 铜铃 / 铃声 | Heartbeat(心跳) | Leader 周期性发送心跳维持权威,Follower 通过心跳判断 Leader 是否存活 |
| "三盏茶没摇铃就推举新主席" | Election Timeout(选举超时) | Follower 在随机超时时间内未收到心跳,就发起选举 |
| "四个赞成算通过" | Quorum(多数派) | 7 个节点的过半 = 4。任何操作必须得到多数节点确认 |
| 议事簿 | Raft Log(日志) | 所有操作按顺序记录在日志中,每个条目有唯一索引 |
| 第一条、第二条、第三条编号 | Log Index(日志索引) | 日志中的条目按递增索引号排列,保证顺序 |
| 新主席先补簿子再议事 | 日志复制到新 Leader | 新 Leader 上任后确保自己的日志与前任一致,包含所有已提交条目 |
| "以前通过的决议永久有效" | Committed entries are immutable | 已提交的日志条目永远不会被覆盖,这是 Raft 安全性的核心保证 |
| 过半票数 + 编号不可翻案 | Log Matching Property | 两个日志如果在相同索引和任期号上一致,则该索引之前的所有条目都一致 |
| 风雨夜只到四个人 | 网络分区 / 节点故障 | 部分节点不可达时,集群仍能通过多数派继续工作 |
| 三票赞成不过半 | 多数派的要求 | 即使部分节点故障,操作仍需获得"总节点数"的多数,而非"在线节点数"的多数 |
| "候议"栏 | 未提交的日志条目 | 未获多数确认的条目暂未提交,可能被后续 Leader 覆盖或重试 |
| 外乡长老的问题 | 共识算法的可理解性 | Raft 的设计目标就是比 Paxos 更直观、更容易实现 |
为什么这个故事对应 Raft?
"任何时候只有一个人主持"对应 Raft 的强 Leader 模型。 Raft 的所有日志流都是从 Leader 流向 Follower,不存在 Follower 直接发起提案的路径。这个设计极大简化了共识逻辑——你只需要保证 Leader 的日志是正确的。
"主席摇铃告诉大家我还在"是心跳机制的核心作用。 心跳在 Raft 中有双重功能:一是阻止 Follower 发起不必要的选举(维持权威),二是携带日志复制消息(AppendEntries RPC)。
"过半才算通过"是 Quorum 机制。 7 个节点的过半是 4。这意味着即使 3 个节点同时故障,集群仍能正常工作。任何两个多数派必然有交集——这保证了不会有两个不同的 Leader 同时提交冲突的日志。
"风雨夜四个人通过了一条,三票通不过第二条"精确对应了网络分区下的多数派行为。 即使部分节点不可达,只要多数派仍在,Leader 就能继续提交日志。但操作必须获得"总节点数"的多数,而非"当前在线节点"的多数。
"新主席上任先补簿子"是 Leader 选举后的日志一致性检查。 新 Leader 必须确保自己的日志包含所有已提交条目。Raft 通过比较日志的索引和任期号来实现这一点——新 Leader 的日志必须至少和前任一样"新"。
"主席可以换,决议不会翻"是 Raft 安全性的最高原则。 一旦某条日志被提交(获得多数确认),任何未来的 Leader 都不能覆盖它。这是 Raft 与一些弱一致性协议的关键区别。
"宁可少记一条,也不记一条假的"反映了强一致性系统对正确性的优先。 Raft 宁可暂时不可用(无法提交新日志),也不会提交一个未获多数确认的条目——这保证了不会出现"假共识"。
后记:分布式共识是计算机科学中最难的问题之一。Raft 的优雅之处在于——它没有试图发明全新的数学原理,而是把 Paxos 的核心思想分解成人脑更容易理解的模块:选主、复制、安全。下次你看到 etcd 里那些编号递增的日志条目时,不妨想想青山镇议事厅里那张泛黄的议事簿——每一条都有编号、都有票数、都永不翻案。共识不是让所有人都同意,而是让所有人都知道什么已经被同意。

