想象你是班里的团建委员。全班 40 位同学分散在几间教室里,群里消息随时会延迟、丢失甚至乱序。你们每周要定一个目的地:公园还是博物馆?最头疼的是:小 A 拉到了 21 人的票投公园,小 B 也拉到了 21 人投博物馆,两边同时宣布“我们赢了”——到底听谁的?Paxos 就是一套聪明到极致的投票规则。它发现了一条铁律:只要两拨人都拉到超过半数的票,这两拨人必然有交集,至少有一位同学同时收到了两边的消息,能把“已经定下的结果”传过去。利用这个“多数派必相交”的特性,Paxos 保证全网最终只认一个结果,绝不会出现“公园和博物馆同时当选”。更妙的是,它把决策拆成“先小声问一圈旧账、再正式举手表决”两步,让后来提出的新提议只能继承旧结果、不能推翻旧结果。Multi-Paxos 则把这场每周一次的投票改造成流水线:选出一个固定的“班长”连续拍板,效率成倍提升。今天我们就用这场团建投票,把 Paxos 从头到尾讲透。
共识算法是分布式系统的“定盘星”,也是面试中从初级到高级的分水岭。搞懂 Paxos,你才能真正理解 etcd、ZooKeeper、Chubby 为什么可靠,并在系统设计面试中从容应对。这条路很陡,但值得爬。
可视化演示
展示Basic Paxos两阶段协议、Multi-Paxos日志复制优化及Quorum法定人数机制,支持模式切换。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Basic Paxos两阶段协议 Prepare阶段Proposer提案获取多数承诺 Accept阶段Proposer发送Accept请求 N > f, Quorum = floor(N/2) + 1 Learner从多数派Acceptor学习最终值
共识问题与多数派思维
把全班 40 位同学想成 40 台服务器,团建群就是网络。你和小 A 同时在群里喊话拉票,可消息会丢、会迟到、会乱序。你俩都以为“拉到一半以上的人就算赢”,但如果两拨人收到的消息不一样,就可能出现两个“过半”同时宣称胜利。直到有人发现:只要两边都超过半数,就必然有一个同学同时收到了两份“投票邀请”,他就是化解冲突的关键。
分布式系统里的情况一模一样:每台服务器都有自己的内存和时钟,无法瞬间看到全局状态。要让所有节点对同一个值达成一致——比如“谁是主节点”“这笔转账是否有效”——只能靠收发消息,而消息恰恰是不可靠的。Paxos 的提出者 Leslie Lamport 把这个难题抽象成“议会立法”:一群议员要投票通过法案,消息会迟到甚至丢失,但最终必须只有一个法案生效。
Paxos 的解决方案立足于一条数学铁律:任何两个“超过半数”的集合必然相交。这就是把“过半民主”升级为“多数派相交”的关键思维——单单比人头不够,还要利用集合必然重叠这一点,让新结果永远无法绕开旧结果。
回到团建投票:你拉到 21 票,小 A 也拉到 21 票,两群人必然有重叠的同学。他的一句话就能让两边知道“公园已经定了”。多数派相交,就是整座 Paxos 大厦的第一块地基。
- \(\mathcal{Q}\) 多数派集合把所有“超过半数节点”的子集收集起来组成一个大集合。
- \(\forall Q_1, Q_2\) 任意两个多数派从集合中任取两拨节点,不关心它们各自具体是谁。
- \(Q_1 \cap Q_2\) 取交集找出同时出现在这两拨节点里的共同成员。
- \(\ne \varnothing\) 交集非空结论是交集不可能为空——两拨人一定有一个共同的同学。
三大角色模型
一次团建决策需要三种人:出主意的发起人、被咨询并表态的评审团成员、负责写周记的记录员。同一个人可以既是评审又是记录员,全看谁有空。
Paxos 同样定义了三个逻辑角色,它们可以由同一个进程兼职。Proposer(提议者)负责发起提案、推动决策;Acceptor(接受者)负责存储状态、参与表决,是决策的真正“记忆载体”;Learner(学习者)负责把已经选定的结果传播给其他节点。工程上,通常所有节点都同时扮演 Acceptor,其中一个节点兼任 Proposer,形成“班长”。
Acceptor 的职责极其单纯:它只需要记住两笔账——“我承诺过的最大提案编号是多少”和“我真正接受过的(编号,值)是什么”。正是这种极简的状态设计,让 Paxos 的正确性可以被严格证明。
评审团成员其实只记两件事:我见过的最大会议编号是多少、已经圈定的地点叫什么。记好这两笔账,后面的所有承诺规则都会顺理成章。
- \(s_a\) 接受者的状态用 \(s_a\) 表示接受者 \(a\) 在当前时刻的全部记忆。
- \(r_{\text{promised}}\) 已承诺编号这个接受者见过并承诺过的最大提案编号,初始为 \(-\infty\) 或空。
- \((r_{\text{acc}}, v_{\text{acc}})\) 已接受记录它实际接受过的提案编号和对应的值,初始为空。
- 只增不改两条记录都只会被更大编号覆盖,绝不会回退到更小编号。
编号与承诺机制
班级事务太多,你给每个提议都盖一个编号:001、002、003……编号越大代表消息越“新”。你上一秒定下的“公园”,绝不能被一张迟到的、盖着 001 号的旧纸条推翻。这就是编号的意义:给时间排序,让旧消息失去作祟的能力。
Paxos 用全序的提案编号为每一轮决策排序。为了保证编号全局唯一且可比,工程上常用(任期号,节点 ID)拼接成一个整数——任期号是主维度,节点 ID 用来打散并列。编号不需要连续,只需要严格递增。
Promise(承诺)机制是安全性的核心。当接受者收到编号更大的 prepare 请求时,它会承诺两件事:第一,以后任何编号更小的 accept 请求我一律拒绝;第二,我在回复时把自己知道的历史账本如实上报。这个承诺像一道时间屏障,把“旧决定”和“新提案”隔离开。
这位同学说:“我见过的最大编号是 005,任何小于 005 的纸条请别烦我。”旧纸条就无法把已定的结果改掉——承诺机制就是一道时间屏障。
- promise\((r_p)\) 承诺事件接受者对编号 \(r_p\) 的 prepare 请求给出的肯定答复。
- \(\iff\) 当且仅当承诺发生的条件被精确限定,不多也不少。
- \(r_p > r_{\text{promised}}\) 编号严格更大只有当新编号大于已承诺编号时才答应,等于也不行。
- 承诺的效力答应之后,任何编号 \(\le r_p\) 的 accept 都会被拒绝,已选定的值由此得到保护。
两阶段提交流程
有了编号和承诺,发起人开始正式操作。关键的一步是:他先不急着表决,而是先“翻旧账”。就像你拿到一个新提案,先在群里小声问一句:“之前定过什么没有?”等多数派回复完毕后,你才发正式表决。
这就是 Paxos 著名的两阶段:阶段一 Prepare,向多数派发送 prepare(n) 请求,收集 promise 和它们的历史已接受记录;阶段二 Accept,根据历史确定最终值 v 后,再向多数派发送 accept(n, v),多数派接受即宣告选定。如果阶段一拿不到多数派承诺,就增大编号重试。
两阶段的灵魂是“值绑定规则”:如果在 prepare 回复中发现任何已接受的历史值,提议者必须沿用其中编号最大的那个,不能自由发挥。这一条让新提案只能“继承”旧结果,从根上杜绝了篡改。
先问旧账再表决,让新提议只能“继承”不能“篡改”。班级记录员把结果写进周记,所有人都不再为同一个地点反复争吵。
- \(v_p\) 最终提交值提议者经过两阶段后真正要提交给多数派的值。
- \(v_h\) 历史值某个接受者回复中携带的、曾经被接受过的值。
- 编号最大从所有历史记录中挑出编号最大的一条,取其值。
- \(v_{\text{new}}\) 自由新值只有当集合为空、没有任何历史记录时,才能选一个全新值。
正确性分析
团建规则有两条底线:已定下的目的地不许反悔——这是安全性;讨论不能无限期拖下去——这是活性。Paxos 对前者给出了严格证明,对后者却“爱莫能助”。
Paxos 的安全性由不变式保证:一旦某个值被多数派选定,之后任何一轮提案被选择的值都必然与它相同。前面所有机制——编号全序、Promise 承诺、值绑定规则——层层嵌套,最终构成了这个不变式的数学证明。证明思路是归纳法:从第一轮开始,若值 v 被选定,则所有后继轮次在 prepare 阶段都会撞见这个值,并被迫继承它。
就像班里两个爱抢风头的同学轮流提出更大编号的议案,会议可以永远开不完。So 大家干脆选一个班长,让他唯一发言——这正是 Multi-Paxos 的出发点。
\left( \textsc{Chosen}(n, v) \;\wedge\; \textsc{Chosen}(n', v') \;\wedge\; n' > n \right) \Rightarrow v' = v- Chosen\((n, v)\) 选定谓词表示编号 n 的提案已经被多数派接受,值 v 胜出。
\textsc{Chosen}(n, v) - 编号比较 \(n' > n\)后一轮提案拥有更大的编号,代表它“更新”。
- 蕴含结论 \(v' = v\)前提同时成立时,后选定的值必须与先前完全一致。
- 安全性 vs 活性这条不变式只保证“不会选错”,不保证“一定能选出”。两个提议者轮流增大编号,协议会活锁。
Multi-Paxos 优化
每周团建都要从“翻旧账”开始,实在太蠢了。于是大家约定:选一个班长,班长上任那天问一圈、拿到所有承诺之后,之后每周直接表决、记录。新班长上任才需要重新翻旧账,平时直接流水线推进。
Multi-Paxos 面对的不再是“一个值”,而是“一串值”:把日志的每个槽位当作一次独立的 Basic Paxos 决策。当 leader 稳定时,多个槽位共享同一轮 prepare——班长上任时的承诺对所有槽位都有效,阶段一被大幅省略,只剩连续不断的 accept 消息,形成流水线。
班长掉线了,全班就要重新选班长;新班长第一件事是先翻开周记本,把已提交的日志补全,再继续往后写。Multi-Paxos 没有发明新算法,它只是把旧规则用在了“一串连续决策”上。
L = [\, v_1,\ v_2,\ v_3,\ \ldots,\ v_i,\ \ldots\, ], \qquad v_i = \textsc{Paxos}(i)- \(L\) 日志全部历史决策的有序序列,就像班级的周记本。
- \(v_i\) 槽位值第 \(i\) 个槽位对应的值,即第 \(i\) 周的团建目的地。
- Paxos\((i)\) 单轮共识每个槽位独立执行 Basic Paxos 算法选出自己的值。
v_i = \textsc{Paxos}(i) - 共享 Prepare同一 leader 在位时,多个槽位复用同一次 Prepare 得到的承诺。
工程实践与学习总结
这套投票规则不是纸上谈兵。Google 的 Chubby 用它提供分布式锁和元数据服务,腾讯的 PaxosStore 用它支撑存储系统的一致性,ZooKeeper 的 ZAB、etcd 的 Raft 也都是这个家族的子孙。可以说,Paxos 的子孙撑起了现代分布式系统的半边天。
Raft 常被称为“把 Multi-Paxos 讲成人话”的算法:它把 Multi-Paxos 中隐晦的领导权、日志连续性、任期投票拆成了清晰易懂的模块,让工程实现有了可遵循的教科书。理解 Paxos,你就拥有了看穿 Raft、ZAB 等一切共识协议的“透视眼”。
真正的工程难点从来不在算法本身:状态必须落盘才能容灾,日志要快照压缩,成员变更要小心处理,乱序和重复消息必须去重。班级章程只有一页纸,但要真的运营好,还需要记录员把每一笔写进账本、团长换届时严格交接。从看懂 Basic Paxos 到写好生产级实现,中间隔着大量细节——这也是你从“知道”走向“掌握”的必经之路。
- Raft 算法一个面向工程可实现性的共识算法。
- Multi-Paxos 内核Raft 复用了“选班长 + 日志槽位”的核心结构。
- 强领导日志只能由 leader 追加,交给 leader 判定提交,杜绝乱序。
- 日志连续性约束要求日志索引严格连续、不空洞,从而简化了选举后的对齐。
AI 应用场景
分布式训练参数多副本同步
阶段:模型训练
解决的问题:大模型训练中,参数服务器的多副本需要保持同一份权重。传统主从异步复制在网络分区时可能产生“双主”,两个机房各训各的,模型参数分叉导致训练发散。用类 Paxos 共识决定哪个副本是权威,多数派一致才能切换,从根上消除脑裂。
具体案例:一家公司用 3 副本参数服务器训练百亿参数模型。某机房网络故障后,旧主节点失联;共识协议自动选出新主,且只有获得多数派确认的新主才接受写入。恢复后旧主重新加入,发现自己已被“更小编号”的决策淘汰,自动同步最新权重,训练无损继续。
模型版本发布的全局一致性
阶段:模型部署上线
解决的问题:推荐系统每天多次热更新模型。如果一部分节点已经切到新版本、另一部分还在跑旧版本,同一用户的两次请求会得到互相矛盾的推荐结果。用共识协议让全集群对“当前生效的模型 ID”达成一致,发布和回滚都像一次原子操作。
具体案例:某广告推荐团队用 5 节点配置中心存储“当前模型版本号”。发布新模型时,版本号作为“值”提交给共识集群;只有多数节点确认后,全网才感知新版本。回滚同理——回滚也是一个新提案,它会继承“已被选定的值”的约束,确保不会出现版本跳跃混乱。
特征存储主备自动切换
阶段:数据管道与特征服务
解决的问题:实时特征服务跨机房部署,主节点一旦宕机必须秒级切换。传统心跳检测在分区时可能误判,出现两个主节点同时写特征库。Paxos 保证分区后只有多数派一侧能继续选主,写入永不分裂。
具体案例:一家金融风控公司把特征库部署在 3 个可用区,用类似 Paxos 的协议做选主。一次光缆被挖断导致一个可用区失联,剩余两个可用区仍构成多数派,自动选出新主继续服务;孤立区的旧主发现无法获得多数派支持,主动降级为只读,避免脏写。
Python 演示
# -*- coding: utf-8 -*-"""Basic Paxos 最小可运行模拟:3 个接受者 + 2 个提议者"""import sys class Acceptor(object): def __init__(self, node_id): self.node_id = node_id self.promised = -1 # 承诺过的最大编号 self.accepted = (-1, None) # 已接受的 (编号, 值) def prepare(self, n): if n > self.promised: self.promised = n return ("promise", self.accepted) return ("reject", self.promised) def accept(self, n, v): if n >= self.promised: self.promised = n self.accepted = (n, v) return ("accepted", self.node_id) return ("reject", self.promised) class Proposer(object): def __init__(self, acceptors, start_round=0): self.acceptors = acceptors self.round = start_round def propose(self, v): self.round += 1 n = self.round # 阶段一: prepare, 收集多数派承诺 promises = [] for a in self.acceptors: resp, acc = a.prepare(n) if resp == "promise": promises.append(acc) if len(promises) < len(self.acceptors) // 2 + 1: return None # 若有历史值, 必须沿用最大编号的那个 latest = max([p for p in promises if p[1] is not None], default=(-1, None)) value = latest[1] if latest[1] is not None else v # 阶段二: accept 提交给多数派 for a in self.acceptors: a.accept(n, value) return value def main(): acceptors = [Acceptor(1), Acceptor(2), Acceptor(3)] p1 = Proposer(acceptors) print("第一轮提议公园 -> 选定:", p1.propose("公园")) p2 = Proposer(acceptors, start_round=10) print("第二轮想提博物馆 -> 实际:", p2.propose("博物馆")) print("所有接受者状态:", [(a.node_id, a.accepted) for a in acceptors]) return 0 if __name__ == "__main__": sys.exit(main())
self.promised = -1接受者的承诺编号初始化为 -1,表示“从没见过任何提案”。def prepare(self, n)只有收到更大编号时才更新承诺并上报历史账本。if n > self.promised:这就是 Promise 规则:编号严格更小的一律拒之门外。if len(promises) < len(self.acceptors) // 2 + 1:凑不齐多数派就放弃本轮,等待重试。value = latest[1] if latest[1] is not None else v值继承规则的核心:有历史值就沿用。for a in self.acceptors: a.accept(n, value)阶段二把确定好的值正式提交给所有接受者。start_round=10第二个提议者用更大的编号抢权,却无法改变已被选定的“公园”。
常见误区
误区多数派不就是超过一半吗?只要每次投票拉到一半以上的人就行,这两个“一半”之间可能完全没有关系。
正解两个超过半数的集合必然相交——这是最简单的抽屉原理。正因为任意两个多数派必有公共节点,共识才不会出现两个同时成立的结果。多数派的本质不是“人数多”,而是“任意两拨必相交”。
为何常见:日常投票的直觉是“比谁人多”,很少有人会从集合交叠的角度去想问题,而这恰恰是 Paxos 安全性的根。
误区阶段一就像是先争取支持者,看看谁愿意投我,然后阶段二让这些人正式投票,谁能拉到多数谁就赢。
正解Prepare 的真实目的是“翻旧账”:查明此前是否已有被选定的值,并让接受者做出承诺。若有历史值,提议者必须沿用,不能自由选新值。拉票只是表象,防止旧结果被篡改才是本质。
为何常见:两阶段看起来很像“预热 + 投票”的拉票流程,人们自然把 prepare 当成私下拉票,忽略了它携带历史信息、锁定承诺的核心义务。
误区只要所有节点都按协议办事,Paxos 就一定能选出值,这是算法最基本的承诺。
正解Paxos 严格保证的只有安全性——已选定的值不被推翻。活性需要额外条件:两个提议者轮流用更大编号抢占,就会活锁,理论上可能永远选不出来。工程上正是靠 Multi-Paxos 选举 leader 才打破僵局。
为何常见:教材几乎都强调“达成一致”的目标,读者很容易把目标的期望当成算法实际证明的性质。
误区Basic Paxos 解决单个值,Multi-Paxos 是另一套全新的共识算法,两者关系不大。
正解Multi-Paxos 就是把 Basic Paxos 反复应用到日志的每个槽位上,本质还是同一个协议。差异只在工程优化:当 leader 稳定时,多个槽位共享同一轮 prepare,跳过了重复的阶段一。
为何常见:名字不同加上论文晦涩,读者以为发明了第二套机制;其实区别只在“会话复用”这个工程层面的优化。
面试题
Q1. Paxos 中一个值被“选定”需要满足什么条件?
- A.所有节点都必须接受该值
- B.超过半数的接受者接受该值← 正确答案
- C.提议者个人单方面决定
- D.全体学习者一致点头认可
答案:B。Paxos 只需要多数派(超过一半)接受就算选定。选“所有节点”是多数派思维的常见误解,多数派相交才是安全性的来源,而非全员一致。
Q2. 接受者在收到更大的提案编号后,会做出什么行为?
- A.拒绝所有更小编号的提议
- B.承诺不再接受更小编号的提议← 正确答案
- C.立即拒绝所有新来的提议
- D.向提议者发送拒绝补偿请求
答案:B。接受者承诺“忽略更小编号”,但不会拒绝更大的。这就像同学说“小于 005 的纸条别烦我”,而不是“所有纸条都别烦我”。B 准确描述了 Promise 的语义。
Q3. Prepare 阶段真正的目的是什么?
- A.抢先获得多数派的投票支持权
- B.查明是否有已选定或即将选定的值← 正确答案
- C.通知所有节点重启并清理状态
- D.测试各节点之间的网络延迟情况
答案:B。Prepare 是“翻旧账”,核心是发现历史已接受的值(即将选定的值),并要求多数派承诺不再接受更小编号。拉票只是表象,携带历史信息才是本质。
Q4. 某接受者已对编号 5 给出 Promise,此时收到编号 3 的 accept 请求,它应当?
- A.继续接受该编号的新值
- B.拒绝该编号并遵守原有承诺← 正确答案
- C.接受但记录一个异常告警
- D.退回编号再同提议者协商
答案:B。承诺一旦做出,接受者必须拒绝所有更小编号的 accept。这是 Paxos 防止旧提案推翻新决定的时间屏障,违反它就破坏了安全性。
Q5. Multi-Paxos 相对 Basic Paxos 的核心优化是什么?
- A.减少参与共识的节点数量
- B.选举固定领导并省略重复准备← 正确答案
- C.引入更强的拜占庭容错能力
- D.彻底取消接受者的投票动作
答案:B。Multi-Paxos 选出稳定 leader 后,多个日志槽位共享同一次 Prepare,省去为每个值重复跑阶段一的开销。节点数量与容错模型并未改变。
Q6. 某接受者已接受值为 W 的提案,此时收到编号更大但携带不同值 V 的 accept 请求,正确行为是?
- A.接受新编号并更新自己的状态← 正确答案
- B.拒绝新编号以保护历史结果
- C.只记录但不通知任何学习者
- D.把决策转发给更高层管理者
答案:A。接受者只检查编号是否不小于已承诺编号,不校验值是否与历史一致。携带不同值说明提议者违反协议假设,但接受者按规则应接受新编号。这道题考察的是“接受者的职责边界”。
Q7. Paxos 算法本身严格保证以下哪种性质?
- A.只严格保证安全性这个性质← 正确答案
- B.同时保证安全与活性两个性质
- C.保证协议在最坏情况下的实时性
- D.保证所有节点最终同时知晓结果
答案:A。Paxos 证明的是安全性(已选定的值不被推翻)。活性只有在额外条件(如选举 leader 消除竞争)下才能获得,两个 proposer 竞争编号可能导致活锁。
Q8. 关于多数派(quorum)相交特性的正确说法是?
- A.任意两个多数派必然拥有公共节点← 正确答案
- B.一个节点可以同时属于多个多数派← 正确答案
- C.两个多数派完全不相交也是允许的
- D.多数派大小等于总节点数的一半
答案:AB。超过半数的两个集合必相交(A 对);一个节点当然可以出现在多个不同多数派中(B 对)。C 违背抽屉原理,D 错在多数派必须严格大于一半而不是等于一半。
Q9. 在 Multi-Paxos 中,稳定 leader 的作用包括?
- A.连续提交提案简化两阶段流程← 正确答案
- B.维护日志槽位的全局顺序← 正确答案
- C.彻底消除集群中的所有故障
- D.替代接受者直接完成值选定
答案:AB。leader 让多个槽位共享 Prepare 并维护日志顺序(A、B)。它不能消除故障,也不能绕过接受者直接选定值——决策仍然需要多数派接受。
Q10. 值被多数派接受并选定后,负责把结果传播给其余节点的是谁?
- A.Proposer 提议者的角色
- B.Acceptor 接受者的角色
- C.Learner 学习者的角色← 正确答案
- D.Client 客户端的角色
答案:C。Learner 的职责就是“学习并传播已选定的结果”,让没有参与投票的节点也能知道最终决定。角色分工是 Paxos 模型的基本知识点。