Paxos共识算法深度解析:分布式高可用的基础机制
凌晨两点多手机震动把我吵醒。群里已经在刷屏了某核心服务的三个副本出现了数据分叉主节点和两个从节点对同一个键值对给出了不同结果。更麻烦的是因为三个副本之间无法达成一致整个服务的自动故障切换也停摆了——写请求被反复重定向读请求开始随机返回旧值和新值。排查到后来问题从“哪条链路写坏了”逐步收敛到一个更基础的问题我们用什么机制来保证多个节点对一个值达成一致以及这个机制在节点故障、网络分区时是否仍然成立。这就是分布式系统里最经典也最绕不开的问题共识Consensus。而 Paxos 共识算法正是最常被提及、也最常被误解的解决方案之一。这篇文章想从一个工程实践者的视角把 Paxos 的原理拆开来讲清楚同时结合它在高可用保障中的真实应用场景做一次深度剖析。无论你是在维护分布式存储、消息队列还是自己搭建复制集群理解 Paxos 的核心逻辑都能帮你把很多“为什么这样设计”的底层问题想明白。1. 从三副本达成一致说起Paxos要解决的真实问题1.1 心跳与冗余还不够高可用必须有“决议机制”很多初学分布式的人容易有一个直观认知只要把数据复制多份一台挂了还有其他节点顶着这不就高可用了吗但真实场景远没有这么简单。多副本只是高可用的前提真正要命的是多个副本之间如何就“谁是主”“数据是什么”“请求如何切换”这些关键状态达成一致。举个例子。三个副本 A、B、C正常时 A 当主节点B 和 C 同步 A 的数据。现在 A 突然宕机了B 和 C 都发现联系不上 A。那么 B 和 C 到底谁应该成为新的主节点如果 B 认为“我联系不上 A所以我当主节点”C 也认为“我联系不上 A所以我当主节点”两个节点同时把自己升为主之后各自接受写请求数据就分裂成两个版本了——这就是脑裂的雏形。更隐蔽的情况是A 其实没有宕机只是网络分区导致它和 B、C 暂时隔离此时 A 还在继续处理请求这就更乱了。所以高可用不是简单地多放几台机器它需要一套在部分节点不可用或网络异常时仍然能做出全局一致决定的机制。这个机制要回答三个问题值是什么、这个值是被谁决定的、什么时候可以认定这个值已经稳定下来了。Paxos 解决的就是这个“决议”问题在一个可能发生故障的分布式环境里让多个节点对某个值达成一致并且保证一旦达成一致这个结果就不会被推翻。1.2 系统模型与故障假设Paxos成立的边界条件分布式领域里的协议通常都建立在明确的模型假设上Paxos 也不例外。Paxos 所假设的模型是广为人知的异步、消息可能丢失、节点可能崩溃且恢复、不存在拜占庭故障的模型。这里每个限定词都很关键少一个理解都会偏差。异步模型的意思是消息传递的延迟没有已知的上界一个消息可能几毫秒到达也可能几秒后才到甚至永远不到达。这听起来似乎很危险但其实这恰恰是现实的写照——网络抖动、垃圾回收停顿、负载飙升都可能导致延迟飙升不能依赖“超时时间到了就判定对方挂了”这种粗暴逻辑来设计协议。消息可能丢失意味着协议设计必须考虑重试而重试又可能带来重复消息所以所有操作都要有幂等性和冗余容忍能力。节点可能崩溃且恢复说明协议不能假设“节点永远在线”但也不能假设节点会做恶意事——它只会按协议执行然后可能在某一步骤停下来。在这样一个模型下Paxos 保证了两个核心性质Safety安全性只有一个提案值会被选定节点只有在某个值被真正选定之后才会得知这个值一旦选定后续提案不能覆盖它。Liveness活性只要足够多的节点存活且网络恢复通信协议最终会选出某个值。这两个性质放在一起才构成高可用的底层保障。Safety 保证了系统不会乱不脑裂、不分裂Liveness 保证了系统不会死最终能恢复服务。我在首次接触时也容易困惑为什么 Paxos 的安全性这么强原因在于它巧妙地用“法定人数 提议编号”两个机制让任何两轮决议之间都存在必须交叉的节点从而从数学上排除了冲突的可能。2. 提案者、接受者、学习者角色划分与法定人数的数学逻辑2.1 三类角色与它们各自的职责Paxos 将参与共识的节点抽象为三种角色Proposer提案者、Acceptor接受者、Learner学习者。这三类角色在真实系统里不一定对应三台独立机器一台机器往往会同时承担多个角色。但为了讲清楚算法我们先严格区分着看。Proposer 是发起决议的节点它负责提出提案Proposal一个提案包含一个提案编号和一个提案值。提案编号全局有序、单调递增是 Paxos 协议里最核心的约束条件提案值才是我们真正想达成的那个值比如“新主节点是 B”或者“把键 X 设置为 5”。Acceptor 是负责表决的节点它收到 Proposer 的请求后会根据自己当前的承诺状态返回响应。提案只有在被法定数量的 Acceptor 接受后才算是真正被选定。Learner 不参与表决只是学习已经被选定的值然后执行后续动作比如把选定的日志条目应用到底层状态机。角色之间的消息交互构成了 Paxos 的两个阶段。这里要强调一点在协议执行过程中一个节点完全可能同时是某轮决议的 Proposer又是另一轮决议的 Acceptor。这一点在实现 Multi-Paxos多轮 Paxos时特别常见因为每个节点都可能成为领导者Leader来发起一轮新的共识。2.2 为什么过半就能达成一致法定人数的抽屉逻辑Paxos 最常被人提起的特性就是“少数服从多数”只要超过一半的 Acceptor 接受了某个值这个值就被选定。但要真正理解 Paxos不能只停在“多数派”的直觉层面还要想清楚为什么多数派这一点在数学上就足够了。假设集群有 5 个 Acceptor。某一轮决议中提案 P 拿到了节点 1、2、3 的接受这样 P 就被选定了。如果之后又有另一个 Proposer 想提出新提案 Q它必须先在 Prepare 阶段联系任意多数派节点。请注意任意两个多数派集合之间必然至少有一个公共节点——这就是抽屉原理的变体。比如 5 个节点的多数派至少是 3 个节点两个 3 节点集合的交集不可能为空。这意味着如果 P 已经被选定那么之后任何一轮 Prepare 都会遇到至少一个“曾经接受过 P”的节点。这个节点会告诉后来的 ProposerP 的编号是多少、值是什么后来的 Proposer 就必须尊重 P不能在未被告知的情况下提出一个不同的值。这个性质还有个优雅的推论一旦一个值被选定后续任何成功的决议轮次都只能选定同一个值。哪怕后来的 Proposer 一开始并不知道前面发生过什么只要它严格按照协议先做 Prepare、采集中间节点状态再决定提案值最终结果都只会是那个已经被选定的值。这就是 Paxos 安全性的根基。法定人数的取值公式也很常见若系统需要容忍 F 个节点同时故障那么 Acceptor 总数 N 需要满足 N ≥ 2F 1多数派的大小为 F 1。比如 3 节点集群容忍 1 个节点故障5 节点容忍 2 个7 节点容忍 3 个。这个关系不是凭空拍脑袋而是确保“多数派仍然存活”与“旧决议信息得以保留”这两个条件同时成立的最小值。3. 两阶段交互的完整推演Prepare与Accept如何保证不乱套3.1 提议编号所有承诺和拒绝的前提进入两阶段细节之前必须先单独把提议编号拎出来讲透。Paxos 里的 Proposed Number通常记作 N不只是一个普通的编号它承担了双重职责既要全局唯一又要全序可比较。全局唯一性用于避免不同 Proposer 撞出相同编号全序可比较用于让 Acceptor 在多个提案之间做出排序判断。工程实现中一个常见做法是用“节点标识 轮次号”的组合来生成编号。比如每个 Proposer 对应一个唯一的高位字节低位字节是该 Proposer 自增的轮次号。这样做既保证了唯一性也保证了可比较性。另一个关键细节是编号的增长没有上限实际实现会用 int64 并在溢出前做封装回绕处理虽然极端罕见但在长期运行的系统中确实是需要考虑的问题。在协议里编号决定了 Acceptor 的承诺逻辑。Acceptor 会记住它见过的最大编号记为 maxPrepareID并且承诺不再接受任何编号小于 maxPrepareID 的 Prepare 请求也不再接受任何编号小于已接受提案编号的 Accept 请求。同时它还会记住自己已经接受过的编号最高的提案记为 acceptedID 和 acceptedValue如果它有的话。这套“承诺-记录”机制保证了后续轮次能够获取到之前决议的关键信息。3.2 一个提案从提出到被选定的完整过程我用一个最常见的方式描述 Paxos 的执行流程也就是两阶段提交协议。为了容易记忆可以把这两个阶段分别叫做准备阶段Prepare和接受阶段Accept。Phase 1aPrepareProposer 选择一个提案编号 N向多数派 Acceptor 发送 Prepare 请求请求中只带编号 N不带值。注意这一点第一阶段的目的不是提交值而是探测网络里其他节点已经知道的消息以及让这些节点做出承诺。Phase 1bPromiseAcceptor 收到编号 N 的 Prepare 请求后会比较 N 和自己的 maxPrepareID。如果 N ≤ maxPrepareID则直接拒绝这个 Prepare 请求并告知 Proposer 自己当前的最大编号如果 N maxPrepareID则更新 maxPrepareID N并承诺不再接受任何编号小于 N 的提案。同时如果 Acceptor 之前已经接受过某个提案要在 Promise 响应里带上那个已接受提案的编号和值如果没有则返回空值。Phase 2aAccept当 Proposer 收到超过半数的 Promise 响应后它就可以准备正式提交值了。这里有一个非常重要的决策规则如果这些 Promise 响应中没有任何一个携带了已接受的值Proposer 可以自由选择自己要提交的值比如新主节点的地址如果存在至少一个携带了已接受的值那 Proposer 必须选择其中编号最大的那个值作为自己的提案值不能自由发挥。Phase 2bAcceptedAcceptor 收到 Accept 请求后再次检查编号 N。只要 N ≥ maxPrepareID它就接受这个提案记录下N, Value并通知所有的 Learner包含 Proposer“第 N 号提案的值已经被我接受了”。当多数派 Acceptor 都已接受后这个值就正式被选定ChosenLearner 们会把这个结果应用到自己的状态机上。这个流程看起来只有两个阶段但它能在各种异常情况下依然不违反安全性是非常精巧的设计。为了加深理解可以手动走一个完整场景5 节点集群 A、B、C、D、E客户端想写入值 V。Proposer 选定编号 1向所有节点发 Prepare。如果所有节点都活着且没有历史记录它们都会 Promise编号 1。Proposer 发现没有任何节点携带旧值于是自由选择 V发 Accept。节点们接受 V超过半数后V 被选定。看起来很简单对不对但 Paxos 的价值恰恰体现在“有节点已经接受过旧值”的场景里。再走一个场景假设在 Proposer 发起编号 2 的 Prepare 时B 节点已经在之前的轮次里接受过一个编号为 1、值 U 的提案。那么 B 的 Promise 响应会携带 (1, U)。Proposer 看到响应里存在编号最大的旧提案 (1, U)它就必须把 U 作为自己的提案值去提交而不能提交 V。这样一来即使之前的决议轮次没有被完整执行完毕最新的一轮也会接过旧的候选值继续推进而不会另立新值。这正是 Paxos “一旦选定、不可推翻”性质在实现层面的保证。3.3 节点崩溃与重复消息消息可丢失模型下的鲁棒性从理论推导切换到工程视角我们必须面对两个现实问题节点可能在任意时刻崩溃消息可能重复或丢失。Paxos 在这个模型下依然正确的关键就在于所有决策都只依赖“多数派已经回应”这一事实不依赖某一个特定节点是否存活。假设 Proposer P 发起编号 5 的 Prepare收到了 A、B、C 的 Promise然后 P 在发 Accept 之前崩溃了。这时候没有值被选定一切恢复后可以由新的 Proposer 继续发起更高编号的 Prepare。A、B、C 虽然已经承诺了不再接受编号小于 5 的提案但这是良性的约束不会阻塞任何高编号提案。再假设 P 发出了 Accept但消息在网络中丢失了只有 A 收到了。A 接受了提案但 A 单独不足多数值尚未被选定。后续如果其他 Proposer 发起 PrepareA 会把自己接受过的 (5, V) 信息带回新轮次由新提案接过 V 继续推进。所以即使 Accept 消息丢了大半只要最终存在一个节点保留了这个值的信息后续轮次就有可能让这个值“死而复生”并最终被选定。工程实现中最容易踩的坑是把“收到多数派响应”和“请求处理成功”当成一回事。实际上Paxos 每一阶段的响应都包含编号校验如果 Acceptor 发现编号过期必须拒绝且携带最新编号返回让 Proposer 知道要发起更大编号的新一轮。这一套“拒绝-重试-升级编号”的循环是保证协议在真实网络里能持续推进的引擎。我用伪代码整理一个最小可运行的心智模型便于对照。以下伪代码只描述核心状态与分支省略了并发锁、网络收发细节# 每个 Acceptor 上的状态 max_prepare 0 accepted_id 0 accepted_value None def handle_prepare(proposal_id): if proposal_id max_prepare: return rejected(max_prepare) max_prepare proposal_id return promised(accepted_id, accepted_value) def handle_accept(proposal_id, value): if proposal_id max_prepare: return rejected(max_prepare) accepted_id proposal_id accepted_value value return accepted(proposal_id, value) # Proposer 侧流程 def propose(value): n next_id() responses send_prepare(n) promises [r for r in responses if r.is_promise] if len(promises) majority: return retry_with_larger_id() highest max(promises, keylambda r: r.accepted_id) proposal_value highest.accepted_value if highest.accepted_value is not None else value accepts send_accept(n, proposal_value) if len([r for r in accepts if r.is_accepted]) majority: return chosen(n, proposal_value) return retry_with_larger_id()这段代码的值不在可运行而在于它几乎逐行对应了 Paxos 论文里的状态机转移。把这段逻辑放在单机上跑一遍模拟多种乱序消息胜过看十遍理论推导。4. 从单轮共识到日志复制Multi-Paxos才是工程的起点4.1 状态机复制共识应用在系统里的标准姿势单轮 Paxos 解决的是一次决议但真实系统需要的是一系列决议每个写请求都要对应一个日志条目这些日志条目被各节点以相同顺序执行后副本状态才会一致。这就是**状态机复制State Machine Replication**范式每个节点维护一个确定性状态机和一个日志序列日志的第 i 个条目通过 Paxos 达成一致各节点按相同顺序应用日志后得到相同状态。为什么日志需要编号因为多个写请求是并发抵达的谁先谁后必须全局一致。如果 A 节点先执行事务 X 再执行事务 YB 节点先执行 Y 再执行 X即便每条日志都通过 Paxos 达成了共识最终状态也可能不同。所以必须有一个全局的日志序号Log Index每个序位上的值由 Paxos 决定且每个序位只能被决定一次。这就把“对单值达成一致”扩展成了“对一个增长序列达成一致”。应用这个序列时还会遇到一个“日志空洞”问题可能第 3 条已经被选定了但第 5 条还没被选定。状态机必须保证在空洞补齐之前不能向后应用——否则应用顺序就被打乱了。这个限制是我们在工程中必须显式处理的客户端看到的数据可能有一部分是旧值直到空洞填充完成。4.2 领导者选举与第2阶段消息压缩朴素的 Paxos 每选定一个日志条目就要执行两轮全量消息交互Prepare 一轮Accept 一轮。如果每个写请求都这么做消息量与节点数成正比时延也高得离谱。Multi-Paxos 的优化思路很直观选出一个稳定的领导者Leader让它成为唯一的 Proposer。当领导者稳定时它可以跳过 Prepare 阶段直接对每个日志条目发起 Accept把两轮交互压缩为一轮。这背后的原理是Prepare 的作用原本是探测旧值并刷新承诺。如果所有 Acceptor 都已经对某个编号 N 做过承诺且后续所有提案的编号都 N那么领导者可以假设 Prepare 一定被接受不需要每轮都探测。换句话说领导者只需要在当选时执行一次 Prepare后续所有日志条目共用这一次 Prepare 的结果。这个一次 Prepare、多次 Accept 的流程就是 Multi-Paxos 的核心。工程实现里领导者不再采用两阶段而是用一个简单的循环对每个日志条目生成递增的提案编号直接发 Accept收到多数派确认后就当作已选定。如果收到拒绝说明有编号更大者出现领导者就退位等待新领导者产生。这个“退位”动作在工程上是不可省略的——如果领导者一直重试而不退位系统就会陷入活锁。4.3 日志的顺序、空洞与补齐机制既然日志是按序号逐个决定的那就会遇到各种乱序场景第 5 条已选定第 4 条还没选定或者某个节点落后了很多条日志。Multi-Paxos 处理这些问题的常见做法是领导者维护一个当前日志序号 CurIndex持续复制各节点收到选定的日志后按序提交如果一个节点发现自己的日志比领导者落后太多它会向领导者发送补齐请求领导者返回连续一段日志供其追平。这里要特别说明空洞的判定。日志空洞不是通过 Paxos 协议本身来检测的而是在节点的本地日志状态中体现的一个节点的日志序列里可能存在“已选定但未提交”的空位。在实现层面我们通常会为每个日志条目维护一个状态位未知、已接受、已提交。如果一个节点在已提交位置之前看到了未知状态它必须阻塞更高位置的提交直到空洞被填充。某些实现还会引入“领导者的下一轮强制确认”机制新的领导者会发起一条空操作日志No-op作为当前最高位置的确认信号从而把空洞边界一次性推进。这个细节很容易被初学者忽略但它直接影响故障转移的时延。之前排查那个凌晨故障时我们发现正是因为某个副本的日志空洞一直没被补齐导致它成为候选主节点后无法立即对外服务整个切换经历了一段很长的阻塞期。后来对空洞补齐逻辑做了触发式修复故障转移才回到亚秒级。5. 高可用保障的应用剖析故障切换、脑裂预防与工程选型5.1 3节点集群最少要几个副本工程参数怎么定谈到高可用第一个被问的参数通常是集群到底要多少个副本这个问题要结合容错数和故障模型来回答。如果使用 Paxos 做一致性协议3 节点的多数派是 2可以容忍 1 个节点故障5 节点的多数派是 3可以容忍 2 个节点故障。也就是说容错 F 和总节点数 N 的关系是 N 2F 1。为什么不用 2 节点因为 2 节点的多数派也是 2等于说你必须两台都活着才能达成决议没有任何容错能力——这还不如不做冗余。所以基础选择是 3 节点这也是很多分布式存储和协调组件默认的配置。在工程选型时你还需要考虑故障域的问题3 节点如果都放在同一个机架里机器故障时可以容忍但机架掉电就全没了。所以真正务实的做法是把节点分布在不同的可用区Availability Zone每个故障域一个副本。5 节点集群则可以在三个可用区里按 2:2:1 分布既保证容错又允许单个可用区整个故障时依然有多数派存活。这里有一个从实际故障中获得的经验副本数不是越多越好因为共识协议的写放大和节点间通信量随副本数线性增长。3 节点能解决大多数场景5 节点适合需要跨机房冗余的关键业务7 节点只在极少数对可用性要求极高的场景出现。再多就得不偿失了。5.2 一次主节点故障的完整切换过程Paxos 在实际系统里往往不直接暴露给客户端它更常见的形式是作为“选主 日志复制”的底层组件。当主节点 A 故障时一次合格的自动切换应该包含以下步骤。第一步是故障探测。其他节点通过心跳或租约感知到 A 不再响应。这里有个关键点不能只凭一个节点判断 A 故障否则可能因为网络抖动误切换。常见做法是让多数派都认可“A 已不可用”或者由一个独立的监控组件发起切换请求。第二步是发起新的决议。B、C、D 中某个节点先站出来作为新的 Proposer 发起一轮新的共识目标是把“新主是 B”这个值选定。协议层通过 Prepare 阶段确保如果旧主 A 之前接受过某个值这个值会被保留或覆盖——只有在一个值被正式选定后新主才能开始工作。第三步是租约续期。新主 B 当选后会向多数派申请一个租约Lease所有读写请求只有在租约有效期内才由 B 处理。这样设计的原因是如果旧主 A 其实没死只是网络隔离了它的租约到期后就不能继续写入了从机制上排除了双主同时写数据的脑裂风险。第四步是日志补齐。新主 B 从多数派节点获取完整日志补上自己缺失的条目然后开始正常对外服务。整个流程的耗时取决于补齐日志的量和网络往返次数。在我经历过的一次优化里将补齐日志的批量抓取从每次 1 条改为按批拉取切换时延直接下降了一个数量级。5.3 为什么说Paxos只能解决“部分高可用”必须承认一个容易被忽略的事实Paxos 保证了“多数派存活时系统可用”但并不能保证“任意时刻集群都能对外服务”。比如 3 节点集群中如果两个节点同时故障剩下的单节点既无法形成多数派也无法推进新决议整个集群就进入只读或不可用状态。这不是 Paxos 的缺陷而是 CAP 定理的必然约束在网络分区或多数节点故障时系统必须在一致性和可用性之间做取舍。Paxos 选择了宁可拒绝服务也不提供错误数据。另外Paxos 本身不负责客户端请求的负载均衡、不负责数据快照清理、不负责跨集群复制。这些都属于高可用体系的其他环节不要指望一个共识算法包打天下。正确认知是Paxos 是“可用性保障的地基”但地基之上还需要一整套监控、切换、备份、演练机制才能构成完整的系统可靠性方案。6. 活锁、性能与学习误区我从项目里踩过的坑6.1 活锁不是理论概念它真的会让集群空转活锁Livelock在 Paxos 里是一种特别微妙的现象多个 Proposer 同时发起 Prepare互相打断对方的部署进度最终谁也无法形成多数派。举个例子Proposer P1 发编号 1 的 PrepareP2 发编号 2 的 Prepare两者都先收到部分 Promise。P1 看到自己编号变小被部分节点拒绝于是重新发起编号 3P2 看到编号 2 也受阻重新发起编号 4。如此反复编号越来越大但始终没有值被选定——集群明明所有节点都活着业务却停滞了。这不是空谈。我在评估某个自研复制组件时曾实测过两个节点同时抢主的场景日志里全是 Prepare 拒绝和编号升级十几分钟都没有选出主节点。后来引入的解法很直接限制同一时刻只有一个活跃的 Proposer也就是选主机制。其他节点只在检测到主节点失效时才尝试成为 Proposer并且引入随机的重试退避让多个候选节点错开时间。这其实就是从 Paxos 理论走向 Multi-Paxos 工程实践时必不可少的一步。6.2 工程里的优化三板斧预分配、批量、并行真正生产级的 Multi-Paxos 实现通常在三个方面做优化。第一是预分配编号段领导者一次申请一段编号范围后续提案直接使用避免频繁向编号服务申请。第二是批量打包把多个待写日志条目打包进同一个 Accept 请求减少网络往返次数在高吞吐写入时效果尤其明显。第三是并行提交多条日志可以并发的向不同 Acceptor 发送只要保持日志序号的单调递交顺序即可不必等待上一条被确认后才发送下一条。这三个优化看起来简单但实现时各自有坑。预分配编号段要求编号生成器本身是高可用的否则会变成单点。批量打包要控制包大小太大反而容易触发网络分片和重传。并行提交则要小心应用顺序——确认完成的条目必须按序应用到状态机否则一致性仍然会被破坏。这些坑我几乎都踩过一遍每次都能归结为同一个教训协议的抽象很简洁但工程实现必须把并发、重试、序号、顺序这些细节当成一等公民对待。6.3 学Paxos最常见的三个误区第一个误区是把 Paxos 和 Raft 对立起来看。两者解决的问题是一样的但 Paxos 更抽象、更底层Raft 则通过强制领导者、日志复制、任期选举等机制把一个可读性更强的共识协议直接搭好。现实中很多分布式组件实现的并不是原始 Paxos而是它的变体或简化版。学习 Paxos 的最大价值其实是让你在面对任何共识协议时都能快速识别出它的 Prepare/Accept 影子以及它如何安排法定人数和编号规则。第二个误区是以为理解了流程图就算懂了 Paxos。流程图只能告诉你消息怎么流动但你必须亲手模拟至少三种场景——正常流程、存在旧提案的流程、Prepare 响应部分丢失的流程——才能真正掌握协议为什么这样设计。做一个简单的本地模拟器用上面那段伪代码就能改是最快的学习路径比读十篇文章都有用。第三个误区是低估了租约和时钟的作用。Paxos 本身不依赖时钟做决议但工程系统里的故障切换几乎都依赖租约和时钟同步来防止脑裂。所以不要以为实现完 Paxos 就万事大吉租约的时长、时钟偏移的容忍窗口、网络分区时的行为都需要你额外仔细设计。最后再分享一点个人体会学习和实现 Paxos 的过程中最难的不是理解算法的正确性而是接受“它解决的是有限问题”这个事实。Paxos 没有解决性能问题没有解决容灾演练问题更没有解决运维人员凌晨被叫醒的问题。但它提供了极其坚固的根基——一旦多数派节点对某个值达成了共识你就可以放心地在这个值上构建更高层的逻辑而不必担心它会悄悄改变。这份确定性在分布式系统里弥足珍贵。