Skip to content
Charles Shao
Go back

分布式共识机制 · Paxos 与 Raft 图解

–views

系统一旦上了多副本,就会撞上同一个问题:在网络抖动与节点故障同时存在、且节点之间没有共享内存的环境里,怎样让多台机器对同一份数据达成一致? 从确认谁是 Leader、把操作写进日志,到最终提交状态,只要要求「多个节点看到同一份全局视图」,底层就会落到分布式里最难啃的一块——共识(Consensus)。高可用 里的选主、Redis 集群故障转移、Kafka 副本可靠性,底层都踩在同一块地基上。

本篇是分布式一致性阅读路径的开篇:先说明为什么必须有共识、异步网络下难在哪里,再把 Paxos 与 Raft 的核心机制拆开讲清楚,最后落到工业选型与常见踩坑。读完之后,再去看 ZooKeeper / etcd 怎么包一层协调服务、分布式锁与事务怎么借共识保正确、以及 ID 生成为何也要谈时钟与唯一性,主线会顺很多。

本文是 分布式一致性 系列的第 1 篇(开篇 · 共识图解)。 全系列 5 篇:

  1. 分布式共识机制 · Paxos 与 Raft 图解(本篇)
  2. 基于共识引擎的协调服务 · ZooKeeper 与 etcd 底层图解
  3. 分布式锁深挖 · Redis Redlock 与 ZooKeeper/etcd
  4. 分布式事务深挖 · 2PC、TCC、Saga 与 Outbox
  5. 分布式 ID 深挖 · 雪花算法及其变体

一句话定位:共识的本质,是让一组可能宕机、丢包或延迟的节点,对「操作指令的执行顺序」达成基于法定人数(Quorum)且一旦选定不可推翻的一致——把松散多副本收成一台逻辑上的复制状态机。

TL;DR

Table of contents

Open Table of contents

1. 复制状态机与多副本一致性痛点

单机最简单:状态在一个进程里,天然一致。但单点故障与垂直扩展上限会把系统推向多副本。副本一多,一致性立刻变成首要问题:多台机器各自维护状态时,如何保证它们对数据的看法一致?

1.1 状态机复制:从多副本对齐到日志定序

工业界的标准做法是状态机复制(Replicated State Machine, RSM)。核心判断是:

若每个副本运行的是同一台确定性状态机(相同输入必得相同输出),那么只要所有副本按严格相同的顺序执行指令,最终状态必然收敛。

于是「多副本一致」被收成一条可落地的工程目标:为所有副本维护一份顺序一致的预写日志(WAL)。状态机按序回放这份日志,一致性随之成立。

复制状态机与多数派提交示意图。

状态机复制:把多副本状态同步转成预写日志的有序复制。共识算法为日志槽位定序并要求多数派落盘,状态机按序回放完成对齐。

后面讲的 Paxos 与 Raft,本质上都在解决同一件事:让一组不可靠节点,对「日志第 i 个槽位该写什么」达成一旦选定就不可推翻的决议。

1.2 CAP 视角:共识机制的 CP 抉择

按 CAP 定理,网络分区(P)不可避免时,必须在一致性(C)与可用性(A)之间取舍。共识算法明确站在 CP 一侧:分区发生时,它宁可让少数派暂停写入,也不允许两侧各自写入造成分叉。后文脑裂隔离的底层逻辑也在这里:用局部可用性换全局安全。

看清这条归约,也就明白共识为什么是分布式系统的地基——选主、动态配置、分布式锁、元数据编排,凡是依赖「全局唯一真相」的场景,底层都要靠它。 系列后续的 ZooKeeper / etcd、锁、事务与 ID,大多是在这层之上再包一层业务语义。

2. 异步网络下的共识困境与 Quorum 机制

2.1 共识问题的形式化定义

抛开状态机外壳,理论上的共识问题可以这样定义:一组进程各自提出提案,系统最终必须**决定(Decide)**一个全局唯一值,并满足:

前两条合起来是安全性(Safety),即不产出错误结果;第三条是活性(Liveness),即系统不会永久卡住。难就难在:故障频繁、延迟无界的异步网络里,要同时兜住这两类指标。

2.2 FLP 不可能定理:理论极限与工程妥协

1985 年,Fischer、Lynch 与 Paterson 给出了分布式理论的基石结论——FLP 不可能定理:

在完全异步模型下(延迟无上界,且无法区分宕机与消息迟到),只要允许哪怕一个进程崩溃,就不存在任何确定性算法能同时保证共识的安全性与终止性。

直觉上:异步链路里永远无法精确判定一个静默节点是已崩溃还是只是延迟。为活性设超时并强行推进,可能误判并造成分叉;为安全无限等待,系统可能永久停滞。

FLP 并不是说「共识做不出来」,而是说纯异步模型下没有「必然终止」的数学保证。工程上常见两条出路:

于是工业共识呈现的是「极大概率很快终止」,而不是「数学上必然终止」——这是对 FLP 的工程折中。

2.3 Quorum 法定人数:共识安全的核心

各类共识协议里反复出现的 ⌊N/2⌋+1(Quorum 多数派),依赖一条简单却关键的结论:

任意两个多数派集合,必定存在至少一个公共节点(交集非空)。

5 节点集群里,任意两个含 3 个节点的集合至少共享 1 个节点。这条「必相交」性质,直接支撑共识安全:

所以少数节点失联时系统仍能安全运行:多数派存活且互通,数据就不会丢、也不会分叉。被隔在少数派一侧的节点凑不齐 Quorum,写入会被拦下。

3. Paxos 算法:从 Basic 到 Multi 的演进

Leslie Lamport 提出的 Paxos,是第一个经过严格证明的共识算法,后续流派大多从这里长出来。它「理论严谨、工程难读」,Lamport 才又写了《Paxos Made Simple》再讲一遍。

3.1 Basic Paxos:角色与两阶段

Basic Paxos 只解决最小问题:让集群对「一个变量」达成一致。协议拆出三类角色(同一进程可兼任):

流程靠两阶段,并绑定全局单调递增的提案编号 n:

阶段一:Prepare(探查并锁定)

阶段二:Accept(写入)

3.2 Paxos 为什么难读

安全关键在第二阶段的约束:「Proposer 必须继承已被多数派接受过的最高编号历史值」。它保证「某个值一旦选定,后续并发提案最终都会收敛到该值」。逻辑严密,但反直觉:

3.3 Multi-Paxos:面向日志序列

要支撑状态机复制,必须对**预写日志的每一个槽位(Slot)**各跑一轮 Paxos。若每条指令都走完整两阶段,吞吐会很差。Multi-Paxos 的核心优化是:

这已经接近「强 Leader + 日志复制」的骨架。但 Multi-Paxos 论文对成员变更、快照压缩、冲突日志覆盖等工程细节留白很多,要靠实现者自己补。Raft 正是在这个背景下出现:Paxos 给出正确理论内核,却缺少开箱即用的工程蓝图。

对比:Paxos 是数学上正确的共识原语;Raft 是面向实现的标准化工程设计。

4. 领导者选举:心跳与随机化超时

Raft 的设计目标写在论文标题里——可理解性(Understandability)。关键设计是强 Leader(Strong Leadership):任一时刻集群至多一个 Leader,写流量经它统一处理,数据流严格从 Leader 单向复制到 Follower。这样避开了 Paxos 里多路提案交叉覆盖带来的复杂度。

4.1 节点状态与逻辑任期

节点在任一时刻必处在三种状态之一:

驱动状态机的核心是 term(任期)——严格单调递增的逻辑时钟。每轮选举抬高 term,同一 term 内至多一个 Leader。在 Raft 里,term 是仲裁标尺:所有 RPC 都带 term;节点一旦发现对方 term 更高,立刻更新本地 term 并无条件降为 Follower。这条规则堵住了「分区孤岛里醒来的旧 Leader」继续写脏数据的路径。

Raft 选举状态机图。

Raft 领导者选举:Follower 选举超时 → 升为 Candidate 并自增 term 拉票 → 获得多数派选票即当选 Leader。随机化超时降低对称僵局,更高 term 始终优先。

4.2 选举流程:随机超时打破对称

换主流程大致如下:

  1. 超时触发:Follower 在**选举超时(Election Timeout)**内收不到 Leader 心跳,判定 Leader 可能失效,转为 Candidate。
  2. 拉票:Candidate 执行 term += 1,先投自己一票,再并行发 RequestVote。
  3. 投票:各节点遵循「同一 term 只投一票(先到先得)」,并校验候选人日志是否「等于或优于自身」(见 §6)。
  4. 当选:Candidate 拿到多数派选票后成为 Leader,立即发心跳压制其他选举。

工程关键是随机化选举超时:若全员固定同一超时,容易同时发起选举,选票被均分(Split Vote),多轮仍选不出。Raft 在区间(如 150–300ms)内随机取超时,让总有节点先醒并先凑齐多数派——这是用随机化化解 FLP 活锁的具体做法。

4.3 心跳与异常降级

现任 Leader 周期性发空 AppendEntries(心跳),刷新 Follower 的超时计时。网络分区时,少数派侧收不到心跳,会不断抬高 term 重试选举。分区愈合后,旧 Leader 重新连上会撞上更高 term,立刻放弃领导权并降为 Follower。自研若漏掉这条降级逻辑,双主(Split-Brain)几乎不可避免。

5. 预写日志复制:从追加到状态机安全提交

Leader 确立后,核心工作变成:把客户端指令安全复制到多数派,再应用到状态机。

5.1 日志结构与 AppendEntries

Raft 日志是串行追加的条目序列(Log Entries),每条形如 {term, index, command}。index 是全局槽位,term 标记写入时的领导者任期——二者一起标识条目,并用于一致性比对。

客户端写入大致走:

  1. Leader 将指令作为未提交条目追加到本地日志。
  2. 构造 AppendEntries(term, prevLogIndex, prevLogTerm, entries[], leaderCommit),并发发给各 Follower。
  3. Follower 做一致性检查:仅当本地在 prevLogIndex 处的任期等于 prevLogTerm 时才追加新条目(保证前缀对齐);否则拒绝,Leader 减小 nextIndex 并回退重试。

Raft 日志复制时序图。

日志复制:Leader 本地追加 → 并行 AppendEntries 落盘 → 多数派 ACK 后推进 commitIndex 并应用到状态机 → 后续心跳带上 leaderCommit 通知 Follower 提交。

5.2 commitIndex:基于 Quorum 的提交边界

日志何时能进状态机,既影响性能,也是安全底线:

一条预写日志,必须在 Leader 确认已落盘到多数派之后,才能标记为「已提交(Committed)」,再应用到状态机并向客户端返回成功。

Leader 用两组游标跟踪同步:

Leader 持续找「已复制到多数派的最高 index」(超过半数的 matchIndex 达到该值),把 commitIndex 推到那里,再驱动状态机。Follower 不必等当次提交回执,而是随后续心跳里的 leaderCommit 得知「该下标之前已全局提交」,再把对应日志应用到本地状态机。

5.3 一致性回退:覆盖未提交的分叉数据

Follower 宕机、分区延迟,或残留冲突日志时,AppendEntries 一致性检查会失败。Raft 的策略是强制向 Leader 对齐:Leader 递减该节点的 nextIndex 试探,直到找到双方前缀吻合的分叉点,再用 Leader 日志覆盖 Follower 分叉点之后的条目。能进状态机的一定是经 Quorum 提交的日志(且已提交不可覆写,见 §6),因此被丢掉的只能是未越过提交线的脏数据,这样做是安全的。

6. 共识安全性约束:选举限制与提交规则

仅有选举与基础复制不够,还要两条安全性约束,才能堵住「已提交状态被脏数据冲掉」的洞。

6.1 选举限制:日志不够新的人不能当选

设想:一台日志严重落后的节点醒来后当选 Leader,再把自己的残缺日志强制同步给全集群,已提交数据就会被毁掉。Raft 用选举限制挡住:

候选人的 RequestVote 必须带上末尾日志坐标 (lastLogTerm, lastLogIndex);投票方只在候选人日志「等于或优于自身」时才投票。「优于」规则:先比 lastLogTerm,任期高者胜;任期相同再比 lastLogIndex,下标更大者胜。

由此可推:当选需要多数派选票,而已合法提交的日志必已覆盖多数派;由 §2.3 交集性质,新 Leader 当选时,其日志必然包含所有已提交历史。这条限制排除了新 Leader「历史倒退」的可能。

6.2 提交规则:切断跨任期误提交

这是 Raft 里最反直觉、自研也最容易踩的一条。表面上,日志复制到 Quorum 似乎就可提交。但 Raft 证明:Leader 不能仅凭「某历史任期的日志已复制到多数派」就直接提交它。这些历史任期条目仍可能被更高 term 的新 Leader 覆盖,造成「看似已提交、随后又被覆盖」的错误回滚。

正确做法:

Leader 只能通过「提交本任期产生的日志」来间接确认更早任期日志的提交。 也就是说,本任期日志过多数派并提交后,其前缀上的历史日志才一并被确认为安全(依赖 Log Matching 前缀匹配与传递性)。

工程上,新 Leader 上任后常会先发一条空日志(no-op entry)并尽快过 Quorum,借此把前朝未决日志一并确认。这条规则与 §6.1 一起,保证 Raft 的状态机安全(State Machine Safety):某个槽位一旦被状态机应用,全集群同槽位不会出现另一种执行结果。

7. 网络分区下的脑裂防御与节点规划

7.1 脑裂隔离与降级

**脑裂(Split-Brain)**指网络把集群切成互不可达的孤岛,两侧若各自选主并写入,就会数据分叉。有了法定人数,共识自带隔离效果:

因此,正确实现的共识集群不会长期双主并存——最坏是少数派保护性中断。许多自研协调件出问题,往往就是底层没把 Quorum 写严,分区后两侧各自为政。

7.2 奇数节点部署

容错能力由 f = ⌊(N-1)/2⌋ 决定(丧失 f 个节点后仍能维持多数派):

节点基数 N法定人数 Quorum容错水位 f
321
431
532
642

规律很清楚:从 3 扩到 4、从 5 扩到 6,容错水位不变,却多了机器成本与故障面。偶数节点收益差。更糟的是,偶数集群遇到 N/2 : N/2 均等分区时,两侧都凑不齐多数派,全局停写(奇数节点任意一刀切,总有一侧仍占多数)。规划共识集群时,用奇数节点(3、5、7),是在给定成本下同时兼顾容错与脑裂防御的常规选择。

8. 共识架构的工程落地与选型

对业务团队来说,多数情况下不必自研底层共识引擎。成员变更、快照压缩、WAL 管理、网络重传等细节,成熟开源组件已经趟过。

选型建议:元数据、核心配置、选主、分布式锁 → 优先 etcd 或 ZooKeeper;新建流处理枢纽 → 可直接用 Kafka KRaft;业务代码里不要自行封装简易共识,把这类能力交给经过生产验证的组件。

9. 强一致性与最终一致性的架构取舍

共识给出强一致性,代价是更高延迟与更严的分区行为。架构上要和最终一致性划清边界,避免把共识用到不该用的地方:

对比维度共识(Raft / Paxos,强一致 / CP)最终一致(Dynamo 风格,AP)
一致性承诺线性一致性(Linearizability):写入对全局可见,单一时序最终一致性(Eventual):允许短暂不一致,后台收敛
写入路径单次写入须经法定多数派确认,延迟更高可向少数副本甚至本地先 ACK,延迟更低
网络分区切断少数派写入(用局部不可用保全局一致)允许两侧继续写,事后用向量时钟或 LWW 调和
典型场景元数据、控制器选主、分布式锁、核心配置、金融对账购物车、点赞计数、弱时效会话、离线分析缓存
代表技术etcd / ZooKeeper / Google SpannerCassandra / DynamoDB / Riak

一句话:共识把「严格时序与唯一事实」做成硬约束,代价是分区期可用性与写入延迟;最终一致性接受短暂不一致,换更高可用与更低延迟。 微服务里二者常并存——共识管小体积核心元数据,最终一致承接海量边缘数据。业务侧冲突调和、幂等与去重,见 幂等与一致性探微。

参考


–views
Share this post on:

Previous Post
Criteo CTR 特征工程实战:把理论串成一条能跑的广告管线
Next Post
广告多维报表与漏斗分析实战