设想你们小组五个人共同维护一份共享文档。起初没有分工,谁都可以改,可你刚把结论改成“周一交”,他马上改回“周五交”,再一刷新,文档变得一团糟。后来大家约定:任何修改都要先在群里喊一声“我要改这里”,等超过一半人同意再落笔。听起来公平,但效率极低——每次改动都要“举手→表决→落笔”,而且两个人同时举手时还会互相打断,谁都无法提交成功。于是大家异口同声说:“干脆先选一个主编!”主编把修改按顺序编号,其他同学负责确认,只要多数人点头就立即生效。这套“选举主编、按序编号、多数确认”的流程,正是分布式系统里大名鼎鼎的 Multi-Paxos 共识协议。 共识协议解决的核心问题是:一堆机器(节点)怎么对一组“修改记录”达成一致,而且任何一台机器挂了都不能让系统崩溃。Multi-Paxos 是其中的经典方案,也是 Raft 的直接前身,很多数据库和一致性组件都受它启发。看懂它,你就拿到了理解分布式数据库、分布式锁、配置中心的一把钥匙。
对 AI 从业者而言,理解 Multi-Paxos 是读懂数据库内核、缓存一致性和分布式训练存储的关键一步,也是后端与系统方向面试时最容易拉开差距的必考点。
目录
可视化演示
展示Multi-Paxos共识协议核心机制:三阶段共识流程(Prepare→Accept→Commit)中Leader与Follower的消息交互,以及日志复制模式下条目提交进度与多数派确认。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Multi-Paxos三阶段共识 Prepare提案编号协商 Accept日志条目复制 Commit多数派确认提交 N ≥ 2f+1 容忍f个节点故障
Basic痛点:会议室里人人抢话筒
回到小组协作的场景。Basic Paxos 相当于一个“没有主编”的规则:任何人要改文档,都得先喊一声“我要改这里”(Prepare),等超过一半同学回一句“行,不改了”(Promise),他才能正式把修改写上去(Accept)。这套规则能保证单次的修改不会冲突,但它有三个很别扭的地方。
第一,每一次修改都要完整跑一遍“举手→表决→落笔”,不能偷懒。你改个错别字要两轮消息,改个标题也要两轮消息,无论改动多小,消息量都一样大。第二,如果两个人同时举手,你举 1 号牌,我举 2 号牌,你的 Prepare 会被我的盖过去,然后你举 3 号牌反超我……两个人就这么互相抬编号,谁也写不成一个字,这就是著名的“活锁”。第三,Basic Paxos 的设计焦点是“单个值”,而不是“一串值”。真实系统里要写入成千上万个值,每个值都重新投票,代价高到无法接受。
在分布式系统术语里,这种“多台机器凑成多数”的概念叫 Quorum。三个节点的集群,至少两个节点确认才有效;五个节点,至少三个节点确认。这个“过半”条件是一切共识协议安全的基石。
所以 Basic Paxos 就像班里人人抢话筒,规则本身没错,可每个人都举着越来越大的编号牌,会场上除了“我要改”“我不同意”之外,什么都没有发生。真正要把系统跑起来,必须改变思路:先安定下来,选一个主编。
q 大于集群总节点数 n 的一半时,表决结果才算数。- q 表示同意者的数量在投票中,真正举手赞成的节点个数就是
q。 - n 表示集群总节点数集群里一共部署了多少台机器,
n就是多少。 - > 表示严格大于不能是“大于等于”,因为恰好一半时双方各执一词,无法形成稳定多数。
- q > n/2 构成多数派条件这个式子说明“过半”是共识的最低门槛,也是活锁与容错能力的来源。
核心优化:选出一位主编,一次准备终身受用
同学们最终发现,与其每次修改都重新争取多数,不如先花一轮功夫选出一位“主编”,让他在任期内负责所有的修改。这个朴素的改进,正是 Multi-Paxos 的灵魂。
Multi-Paxos 的优化思路可以概括为两点。第一,集群先通过一轮 Prepare/Accept 选出一个稳定领袖(Leader)。选出之后,除非 Leader 崩溃或网络分区,否则大家不再轻易换人。第二,Leader 在任期内提交第一个值时才需要 Prepare,后续提交值直接进入 Accept 阶段。因为在选主那一轮,接受者已经把承诺给了这位 Leader,只要它的编号不降,就不必重复询问“我要改这里”。
假设系统要提交 m 条日志条目,Basic Paxos 每一条都要经历 Prepare 和 Accept 两个阶段,而 Multi-Paxos 只需要在任期开始时做一次 Prepare,之后 m 条全是 Accept。消息量立刻从“2m 轮”降为“m 轮加一”,性能差距随 m 增大越来越明显。
主编一旦确定,剩下的就是流水线作业:他把修改一条条编号,大家按顺序确认。会议室从“菜市场”变成了“编辑部”,效率自然天差地别。
t_P 是一次 Prepare 的开销,t_A 是一次 Accept 的开销。Multi-Paxos 把 Prepare 从“每条一次”压缩成“每任一次”。- m 表示要提交的日志条数比如主编要顺手整理 10 条修改,
m就是 10。 - t_P 表示一次 Prepare 的开销Prepare 是“举手请求”阶段,涉及一轮广播和等待回应。
- t_A 表示一次 Accept 的开销Accept 是“正式落笔”阶段,同样需要一轮广播和多数确认。
- 基本式减去复用式Basic 是每条日志都付两笔账,Multi 只付一笔“选主账”和 m 笔“落笔账”。
选主机制:编号牌决定话语权
主编可不是终身制。万一主编请假、宕机,或者网络把主编和其他人隔开了,就需要重新选一个人来主持。问题来了:谁来当新主编?怎么判断一个候选人的资格?
Multi-Paxos 的选主机制像发编号牌。每个候选人手里都有一个单调递增的编号(Ballot Number / 任期号),想要竞选时,他带上自己的编号向其他节点广播 Prepare:“我要当主编,我的编号是 5。” 各节点只认编号更 大 的候选人:如果它们之前已经答应过编号 4,那么编号 5 会得到许可;如果它们已经答应过编号 6,编号 5 就会被拒绝。一旦多数节点承诺只接受编号 5,这个候选人就成了新 Leader。
编号牌机制还附带一个安全效果:陈旧消息自动失效。当选主完成、新主编上任后,旧主编的“落笔”请求因为编号较小,会被所有节点视为过期文件直接丢弃,绝不会覆盖新主编已经推进的内容。
选主就像一场“号牌拍卖会”,最新最大的号牌获胜,旧号牌的记录一律作废。这样一来,无论主编换多少任,同一个时刻都只有一个“最高编号”说了算。
- n_new 表示新提案编号候选人拿来竞选用的新号牌。
- max 表示取最大值在候选人收到的所有回复编号里,找出最大的那个。
- n_observed 表示已观察到的编号候选人能“看到”的别人用过的编号集合。
- +1 表示必须严格更大只准比所有旧编号大,不许平等或更小,否则无法压过旧主编的记录。
日志复制:主编把稿件按顺序编号分发
新主编走马上任后,他要把所有修改整理成一条条“日志条目”。日志条目是分布式系统里的核心载体,每一条都包含三个信息:它排在第几号(索引)、它属于哪个任期(编号)、它的内容是什么(值)。主编在自己的日志末尾追加新条目,然后把条目复制给所有节点。
日志复制的关键在“按序”。主编只会往日志尾部追加,索引严格递增,不会跳过、不会原地修改。每个副本收到条目后,也把它追加到自己的本地日志尾部。如果某个副本反应慢,或者网络分区,主编会不断重试;日志缺口较大的副本,则通过追赶机制补齐。
假设主编的日志目前最长索引是 i,那么下一条新条目的索引就是 i+1。这里的“索引”就是团队里修改记录的流水号,编号 7 永远在编号 6 之后,不会插队。
主编不用等所有同学回复。他只要把稿件按编号发出去,大家收到后各存一份,多收集几张“收到”的回执,就足以确定这条修改能否转正。
- i_append 表示新条目的索引主编要为下一条修改分配的流水号。
- i_log 表示日志中已有索引当前日志里最后一条的索引位置。
- max 表示取当前最大索引日志中间可能有空洞,但主编只用“最大索引”来决定追加位置。
- +1 表示顺序追加新条目永远排在最大索引后面,保证全局顺序唯一。
推进规则:多数人点头才算定稿
日志复制只是把条目送到了各副本手里,真正重要的是“定稿”——也就是提交(Commit)。一条日志条目被提交的含义是:这条修改已经被整个集群认可,可以永久生效,并且任何新主编都不能推翻它。
提交的判定条件非常简单:一个条目只要被超过半数的节点写入并确认,就算提交。为什么“多数”就够了?因为任意两个多数派必然有交集。旧主编提交过的条目一定出现在某个多数派里,新主编无论如何都绕不开这个多数派,所以它要么保留该条目,要么能从中恢复出它的内容。
当某条日志达成多数确认后,Leader 把自己的“提交指针”(commit index)前移到该位置,并把提交信息广播给所有副本。各副本收到提交通知后,把日志中已提交的部分逐条“回放”到状态机——也就是真正执行 set key=1、转账 100 元 这类命令。状态机按照相同顺序执行相同命令,最终各节点状态完全一致。
“多数人点头”让定稿既安全又高效:不要求全员到场,只要过半就能推进。就像开会时不必等所有人都签字,只要超过半数人签名,决议就能生效。
- p 表示某个节点
p是集群中的一台具体机器。 - log[k] 表示第 k 条日志节点 p 的日志数组里下标为 k 的位置。
- |集合| 表示满足条件的节点数统计所有在第 k 位写下 v 的节点一共有多少。
- > n/2 表示超过半数数量达标时第 k 条正式定稿。
Raft异同:把会议室规则写成傻瓜版手册
Multi-Paxos 非常强大,但它更像一篇论文里的“设计思路”,留下很多灰色地带:什么情况下允许空洞?新 Leader 上任时怎么补齐日志?这些细节在工程实现时相当容易踩坑。Raft 的成功正在于它把 Multi-Paxos 的松散规则改写成了严格、可逐条执行的操作手册。
Raft 与 Multi-Paxos 的第一个区别是角色划分。Multi-Paxos 里“提议者、接受者、学习者”三种角色是叠加在同一批节点上的,身份切换很灵活;而 Raft 把每个节点硬性分成 Leader、Follower、Candidate 三种状态,并用状态机限定转换条件。第二个区别是日志匹配(Log Matching)。Raft 强制要求:如果两个节点在同一个索引处有相同任期号,那么之前的日志必须完全一样;Leader 在追加日志前还要检查 Follower 与自己的最近一条日志是否匹配。这条规则在 Multi-Paxos 中并不显式,但 Raft 直接写进了算法。第三个区别是提交规则的收紧:Raft 规定 Leader 不能通过“旧任期日志已经被复制到多数派”来提前提交它,而必须等当前任期的一条新日志也提交成功,才允许一并提交旧日志,避免日志覆盖把已提交的内容回滚。
从这个角度看,可以写出一句便于记忆的公式:Raft = Multi-Paxos + 工程约束。Multi-Paxos 是思想,Raft 是带着安全护栏的落地实现。
把 Multi-Paxos 理解成“会议室治理哲学”,Raft 就是一份写清楚“谁负责签到、谁负责记录、争议如何裁决”的员工手册。现在很多数据库(比如 etcd、TiKV)都选择直接实现 Raft,但它们的血脉里始终流淌着 Multi-Paxos 的思想。
- Raft 表示工程化协议Raft 是一个可直接编码实现的一致性算法。
- MultiPaxos 表示核心思想Raft 沿用了“稳定 Leader + 日志复制 + 多数派提交”的整体框架。
- 日志匹配 表示追加前必须先对齐Leader 每次追加日志前,都要确认与副本的日志前缀一致。
- 选举限制 表示新 Leader 必须包含全部已提交日志避免选出一个“失忆”的主编,把已定稿的修改弄丢。
AI 应用场景
场景一:分布式数据库的日志复制
阶段:训练数据存储与状态同步(数据库内核、分布式存储)
解决的问题:AI 训练任务需要把模型参数、样本元数据存放在多个副本上,传统主从复制在主机宕机时会丢数据或产生脑裂,用 Multi-Paxos 思想选主并复制日志,可以保证“多台机器在任何时刻看到的提交记录都一致”。
具体案例:Google Chubby 锁定服务使用类 Paxos 协议为分布式系统提供粗粒度锁;TiKV 这类 KV 数据库则用 Raft(Multi-Paxos 的工程化改良)在多个 Region 副本间同步数据。当一台训练存储节点宕机,集群会自动选出新 Leader,已经在多数派节点落盘的参数日志不会丢失。
场景二:跨数据中心配置分发
阶段:模型配置中心、特征服务发布
解决的问题:AI 平台需要把模型版本、推理服务路由配置实时下发给几十台在线推理机。接力的方式是手动推送,失败率和误操作率都很高;用共识协议承载配置变更后,只有超半数机房确认的配置才生效,且版本号单调递增,旧配置覆盖不了新配置。
具体案例:某公司在三个可用区部署配置中心,每次发布推理配置时,配置中心通过类似 Multi-Paxos 的流程把“配置版本号 + 变更内容”写入日志。即使一个可用区断网,另外两个可用区依然能确认配置并继续推进发布。
Python 演示
class Replica: def __init__(self, name): self.name = name self.promised = 0 # 已应允的最大提案轮数 self.accepted = [] # 已接受条目的 (轮数, 索引, 值) self.commit = 0 # 已提交索引 class MultiPaxosLog: def __init__(self): self.replicas = [Replica(name) for name in ["A", "B", "C"]] self.leader = self.replicas[0] self.ballot = 1 self.log = [] def prepare_once(self): # 领导者刚上任,只需要做一次Prepare for r in self.replicas: r.promised = self.ballot return sum(1 for r in self.replicas) > len(self.replicas) / 2 def propose(self, value): idx = len(self.log) for r in self.replicas: if r.promised >= self.ballot: r.accepted.append((self.ballot, idx, value)) acks = sum(1 for r in self.replicas if r.accepted and r.accepted[-1][1] == idx) if acks > len(self.replicas) / 2: self.log.append(value) for r in self.replicas: r.commit = len(self.log) return True return False def main(): paxos = MultiPaxosLog() if not paxos.prepare_once(): print("prepare failed") return for cmd in ["set key=1", "set key=2", "del key"]: ok = paxos.propose(cmd) print(cmd, "committed" if ok else "rejected") print("final log:", paxos.log) if __name__ == "__main__": main()
r.promised = self.ballot各副本把本轮编号记入账簿,相当于接受者承诺“只认这个编号的 Leader”。r.accepted.append((self.ballot, idx, value))副本把收到的日志条目记录进本地日志。acks > len(self.replicas) / 2只有确认数量超过半数,条目才算提交成功。self.log.append(value)Leader 将日志推进到新索引,代表提交状态前移。r.commit = len(self.log)所有副本同步更新自己的提交指针,之后可以安全执行已提交命令。
常见误区
误区既然名字叫"Multi"、核心又强调优化,那它是不是彻底删掉了 Prepare?选完 Leader 之后,想提交多少条就提交多少条,不必再谈 Prepare 了。
正解Multi-Paxos 只减少了 Prepare 的次数,并没有删除 Prepare。选 Leader 的那一轮本身就是要通过 Prepare 来完成的;当选主成功后,新 Leader 在任期内才无需为每个条目重复 Prepare。一旦 Leader 变更,新的 Prepare 依然会发生。
为何常见:很多人看到资料里写“Multi-Paxos 跳过 Prepare”就望文生义,忽略了“只在稳定 Leader 任期内跳过”这个前提。
误区只要复制到超过一半的节点,日志就一定安全了,剩下那些少数派节点随便挂、随便丢数据都没关系。
正解多数派安全有一个前提——新 Leader 必须包含全部已提交日志。如果选主时恰好选到一个日志残缺的节点,它可能不知道旧日志的完整内容,从而覆盖掉已经提交的记录。Raft 正是为此增加了“选举限制”。
为何常见:大家记住了 q > n/2 的公式,却没意识到“多数派相交”只是必要条件,还需要选主和日志匹配规则配合。
误区只要多个节点最终看到相同的值就够了,先提交哪条、后提交哪条无所谓。
正解Multi-Paxos 通过“日志索引”把值固定在全局有序的序列里。如果两个节点在同一个索引位置写入了不同的值,后面无论怎么对齐都会产生分歧。因此 Multi-Paxos 不只保证值一致,还强制每条日志的索引唯一且有序。
为何常见:Basic Paxos 只解决“单个值一致”的问题,容易让人误以为 Multi-Paxos 也只是把单值问题重复多次,忽略索引与顺序的关键作用。
误区Raft 是从 Paxos 改来的,那它们本质上是一回事,没必要分开学。
正解Raft 借鉴了 Multi-Paxos 的核心思想,但在工程细节上做了显著收紧:角色被固定为三种,日志追加前必须做匹配检查,提交旧任期日志也有额外限制。不了解两者的差异,遇到实际系统里的怪问题会无从排查。
为何常见:网上很多文章用“Raft 是简化版 Paxos”一笔带过,读者便以为两者可以画等号,忽视了 Raft 增加的约束正是在填补 Multi-Paxos 的工程空隙。
面试题
Q1. 在 3 节点 Paxos 集群中,提交一个值至少需要几台节点确认?
- A.1台节点
- B.2台节点← 正确答案
- C.3台节点
- D.0台节点
答案:B。共识协议要求严格多数派,q > 3/2,所以 3 节点集群至少需要 2 台确认。1 台不够,3 台是“全部确认”而非“多数确认”,0 台则无法达成共识。
Q2. Basic Paxos 在并发情况下出现“活锁”的根本原因是什么?
- A.Prepare 编号不够高
- B.多个提议者反复抢占← 正确答案
- C.接受者拒绝某编号
- D.消息偶尔超时丢失
答案:B。活锁源于多个提议者同时发起 Prepare,彼此用更大的编号互相压过,导致没有一条 Accept 能完成。编号不够高只是表面现象;接受者拒绝机制本身不是原因;消息丢失会导致失败,但不会形成“反复抢占”的死循环。
Q3. Multi-Paxos 相对 Basic Paxos 的核心优化是什么?
- A.增加每轮消息数量
- B.固定Leader减少Prepare← 正确答案
- C.去掉Accept阶段
- D.让副本数变多
答案:B。Multi-Paxos 选出稳定 Leader 后,任期内只做一次 Prepare,之后连续提交都直接走 Accept。它没有去掉 Accept,也没有增加消息量,更不是靠增加副本数量来提速。
Q4. Multi-Paxos 中,如何判断一个日志条目已经“提交”?
- A.Leader本地落盘
- B.多数派副本确认← 正确答案
- C.所有副本均确认
- D.Follower报告成功
答案:B。提交条件是超过半数的节点在日志同一索引写入同一值。Leader 本地落盘或单个 Follower 报告都构不成多数派,要求“所有副本确认”则过于严格且不必要。
Q5. 以下哪些方法能缓解 Paxos 的活锁问题?
- A.引入稳定Leader← 正确答案
- B.增加Prepare随机延迟← 正确答案
- C.增加Accept阶段
- D.随机退避后重试← 正确答案
答案:ABD。引入 Leader 能避免多人同时争抢;随机延迟和随机退避能让竞争者的节奏错开。增加 Accept 阶段不会减少竞争,反而增加消息量,因此 C 不正确。
Q6. 以下哪类节点绝不能当选为新 Leader?
- A.日志最全的节点
- B.日志缺失的节点← 正确答案
- C.编号最高的节点
- D.获得多数投票的节点
答案:B。新 Leader 必须有能力继承所有已提交日志。日志缺失的节点可能丢失旧条目,若它当选并用旧值覆盖已提交内容,会造成数据回滚。这也是 Raft 引入“选举限制”的直接原因。
Q7. Raft 与 Multi-Paxos 的主要区别有哪些?
- A.角色划分更清晰← 正确答案
- B.强制日志匹配← 正确答案
- C.没有Leader选举
- D.提交规则更严格← 正确答案
答案:ABD。Raft 把节点状态固定为 Leader/Follower/Candidate;追加日志前强制检查日志匹配;提交旧任期日志时必须等待当前任期日志提交。Raft 同样有 Leader 选举,因此 C 错误。
Q8. Raft 如何保证已提交的日志条目一定不会丢失?
- A.日志匹配校验
- B.选举限制约束← 正确答案
- C.拒绝旧状态
- D.多数派背书
答案:B。Raft 的选举限制要求新 Leader 必须包含全部已提交日志,这正是防止日志回滚的关键。日志匹配校验是复制阶段的手段,拒绝旧状态和多数派背书都不能单独保证不丢日志。
Q9. Multi-Paxos 日志复制的特点包含哪些?
- A.多条目连续提交← 正确答案
- B.Leader任期免重复Prepare← 正确答案
- C.每个条目都要Prepare
- D.日志条目按序追加← 正确答案
答案:ABD。Multi-Paxos 在稳定 Leader 任期内可以连续提交多个条目,并且只有第一个 Prepare。C 是 Basic Paxos 的行为,不符合 Multi-Paxos 的特点。
Q10. Paxos 协议中"Quorum"实际指的是什么?
- A.超过半数的节点集合← 正确答案
- B.集群全部节点集合
- C.只有Leader的集合
- D.编号最大的节点集合
答案:A。Quorum 指多数派,是任意两个 Quorum 必有交集的最小安全集合。直接取全部节点太慢,只取 Leader 则无容错能力,按编号选集合也不是 Quorum 的定义。