系统一旦上了多副本,就会撞上同一个问题:在网络抖动与节点故障同时存在、且节点之间没有共享内存的环境里,怎样让多台机器对同一份数据达成一致? 从确认谁是 Leader、把操作写进日志,到最终提交状态,只要要求「多个节点看到同一份全局视图」,底层就会落到分布式里最难啃的一块——共识(Consensus)。高可用 里的选主、Redis 集群故障转移、Kafka 副本可靠性,底层都踩在同一块地基上。
本篇是分布式一致性阅读路径的开篇:先说明为什么必须有共识、异步网络下难在哪里,再把 Paxos 与 Raft 的核心机制拆开讲清楚,最后落到工业选型与常见踩坑。读完之后,再去看 ZooKeeper / etcd 怎么包一层协调服务、分布式锁与事务怎么借共识保正确、以及 ID 生成为何也要谈时钟与唯一性,主线会顺很多。
本文是 分布式一致性 系列的第 1 篇(开篇 · 共识图解)。 全系列 5 篇:
- 分布式共识机制 · Paxos 与 Raft 图解(本篇)
- 基于共识引擎的协调服务 · ZooKeeper 与 etcd 底层图解
- 分布式锁深挖 · Redis Redlock 与 ZooKeeper/etcd
- 分布式事务深挖 · 2PC、TCC、Saga 与 Outbox
- 分布式 ID 深挖 · 雪花算法及其变体
一句话定位:共识的本质,是让一组可能宕机、丢包或延迟的节点,对「操作指令的执行顺序」达成基于法定人数(Quorum)且一旦选定不可推翻的一致——把松散多副本收成一台逻辑上的复制状态机。
TL;DR
- 服务于状态机复制(RSM):多副本一致,等价于所有副本「按同一顺序执行同一指令序列」。共识负责为预写日志定序并同步到法定人数,状态机再按序回放,最终状态才会对齐。
- 直面异步网络与故障:进程可能崩溃、消息可能乱序。FLP 不可能定理表明:纯异步模型下,只要允许单节点失效,不存在既能保证安全又能在有限时间内必然终止的确切算法。工程上靠心跳与随机化超时规避,用「极大概率终止」换可用性。
- Quorum 法定人数:共识安全的核心。任意两个多数派必有交集;
⌊N/2⌋+1个节点确认,保证新决议建立在对旧决议的感知之上,部分节点失联时仍可安全推进。 - Paxos:理论奠基,工程门槛高:Basic Paxos 用 Proposer / Acceptor / Learner,经 Prepare / Accept 两阶段与全局递增编号,保证「一旦选定,后续只能认同该值」。它只解决单值共识;要支撑日志流需 Multi-Paxos,而论文对工程细节留白多,落地成本高。
- Raft:工程化拆解:把共识拆成三个正交模块——领导者选举、日志复制、安全性约束,并坚持强 Leader、日志从 Leader 单向复制,认知负担明显更低。
- 领导者选举:用逻辑时钟
term、心跳与随机化选举超时打破对称。候选人拿到多数派选票即当选;随机超时用来压低选票瓜分(Split Vote)导致的活锁。 - 日志复制:Leader 本地追加后,经
AppendEntries并行复制。日志落到多数派并推进commitIndex后才算安全提交,再应用到状态机;用matchIndex/nextIndex跟踪进度,不一致时回退覆盖未提交脏数据。 - 安全性两条硬约束:① 选举限制:只有日志「不落后于投票方」的候选人才能当选;② 提交规则:不能仅凭「历史任期日志已复制到多数派」就直接提交,须由当前任期日志的提交间接确认,防止已提交记录被覆盖。
- 脑裂隔离与节点规划:分区时只有含多数派的一侧能维持 Leader 并继续写,少数派自动降为不可写——这是共识自带的脑裂防御。奇数节点部署可在同等容错下避免
N/2 : N/2均等分区导致全局卡死。 - 落地与选型:etcd / Consul 用 Raft,ZooKeeper 用同源思路的 ZAB,Kafka 用 KRaft 把元数据收进内部 Raft 队列以去掉外部 ZK。核心教训:生产环境不要自研共识引擎,用成熟组件。
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)**一个全局唯一值,并满足:
- 一致性(Agreement):所有非故障进程决定的必须是同一个值(安全)。
- 有效性(Validity):决定的值必须来自某个进程的真实提案(不能凭空造值)。
- 终止性(Termination):所有非故障进程最终都能完成决策(活性)。
前两条合起来是安全性(Safety),即不产出错误结果;第三条是活性(Liveness),即系统不会永久卡住。难就难在:故障频繁、延迟无界的异步网络里,要同时兜住这两类指标。
2.2 FLP 不可能定理:理论极限与工程妥协
1985 年,Fischer、Lynch 与 Paterson 给出了分布式理论的基石结论——FLP 不可能定理:
在完全异步模型下(延迟无上界,且无法区分宕机与消息迟到),只要允许哪怕一个进程崩溃,就不存在任何确定性算法能同时保证共识的安全性与终止性。
直觉上:异步链路里永远无法精确判定一个静默节点是已崩溃还是只是延迟。为活性设超时并强行推进,可能误判并造成分叉;为安全无限等待,系统可能永久停滞。
FLP 并不是说「共识做不出来」,而是说纯异步模型下没有「必然终止」的数学保证。工程上常见两条出路:
- 引入时间假设(部分同步):用心跳超时推断失效。Raft 的选举超时与心跳断连,本质是「超时未响应则视为宕机」的妥协——放弃绝对零误判,换生产里绝大多数场景能推进。
- 引入随机化:用随机数打破并发竞争下的对称僵局(如 Raft 的随机化选举超时),活锁概率随时间指数衰减。
于是工业共识呈现的是「极大概率很快终止」,而不是「数学上必然终止」——这是对 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 只解决最小问题:让集群对「一个变量」达成一致。协议拆出三类角色(同一进程可兼任):
- Proposer(提议者):生成并发送提案。
- Acceptor(接受者):校验并投票,是多数派存储的载体。
- Learner(学习者):学习已选定的值,不参与投票。
流程靠两阶段,并绑定全局单调递增的提案编号 n:
阶段一:Prepare(探查并锁定)
- Proposer 选出大于已知最高编号的
n,向 Acceptor 广播Prepare(n)。 - Acceptor 收到
Prepare(n):若n大于它曾响应过的任何编号,则承诺拒绝之后所有编号小于n的提案,并把已接受过的最高编号提案值回给 Proposer。
阶段二:Accept(写入)
- Proposer 收集多数派的 Prepare 响应。若响应里已有值,必须继承其中编号最高的那份(不能再用自己的原始值);若全空,才可用自选值,然后广播
Accept(n, value)。 - Acceptor 校验
Accept(n, value):只要尚未对更高编号做过承诺,就接受并持久化。 - 某提案一旦被多数派 Acceptor 接受,该值即选定(Chosen),不可逆。
3.2 Paxos 为什么难读
安全关键在第二阶段的约束:「Proposer 必须继承已被多数派接受过的最高编号历史值」。它保证「某个值一旦选定,后续并发提案最终都会收敛到该值」。逻辑严密,但反直觉:
- 后发收敛,而非事前协商:不是先开会再写,而是后发提案撞上历史多数派时,被迫继承旧值——理解成本高。
- 只解决单值:生产需要的是指令队列(日志序列),Basic Paxos 没有直接定义。
- 活性不足:多个 Proposer 交替抬编号并打断对方的 Prepare,会陷入活锁。工程上常靠单一 Leader 规避,但原始论文没写清选主细节。
3.3 Multi-Paxos:面向日志序列
要支撑状态机复制,必须对**预写日志的每一个槽位(Slot)**各跑一轮 Paxos。若每条指令都走完整两阶段,吞吐会很差。Multi-Paxos 的核心优化是:
- 稳定 Leader:固定单一节点作为全局 Proposer。
- 削减 Prepare:Leader 稳定后,阶段一可对连续槽位批量做一次,之后每条指令直接走阶段二(一次 RTT 过多数派),吞吐上去。
这已经接近「强 Leader + 日志复制」的骨架。但 Multi-Paxos 论文对成员变更、快照压缩、冲突日志覆盖等工程细节留白很多,要靠实现者自己补。Raft 正是在这个背景下出现:Paxos 给出正确理论内核,却缺少开箱即用的工程蓝图。
对比:Paxos 是数学上正确的共识原语;Raft 是面向实现的标准化工程设计。
4. 领导者选举:心跳与随机化超时
Raft 的设计目标写在论文标题里——可理解性(Understandability)。关键设计是强 Leader(Strong Leadership):任一时刻集群至多一个 Leader,写流量经它统一处理,数据流严格从 Leader 单向复制到 Follower。这样避开了 Paxos 里多路提案交叉覆盖带来的复杂度。
4.1 节点状态与逻辑任期
节点在任一时刻必处在三种状态之一:
- Follower(跟随者):被动响应 Leader / Candidate,靠心跳重置选举超时。
- Candidate(候选人):选举过渡态,发起拉票。
- Leader(领导者):掌握写入,靠心跳维持地位并压制新选举。
驱动状态机的核心是 term(任期)——严格单调递增的逻辑时钟。每轮选举抬高 term,同一 term 内至多一个 Leader。在 Raft 里,term 是仲裁标尺:所有 RPC 都带 term;节点一旦发现对方 term 更高,立刻更新本地 term 并无条件降为 Follower。这条规则堵住了「分区孤岛里醒来的旧 Leader」继续写脏数据的路径。
Raft 领导者选举:Follower 选举超时 → 升为 Candidate 并自增 term 拉票 → 获得多数派选票即当选 Leader。随机化超时降低对称僵局,更高 term 始终优先。
4.2 选举流程:随机超时打破对称
换主流程大致如下:
- 超时触发:Follower 在**选举超时(Election Timeout)**内收不到 Leader 心跳,判定 Leader 可能失效,转为 Candidate。
- 拉票:Candidate 执行
term += 1,先投自己一票,再并行发RequestVote。 - 投票:各节点遵循「同一
term只投一票(先到先得)」,并校验候选人日志是否「等于或优于自身」(见 §6)。 - 当选: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 标记写入时的领导者任期——二者一起标识条目,并用于一致性比对。
客户端写入大致走:
- Leader 将指令作为未提交条目追加到本地日志。
- 构造
AppendEntries(term, prevLogIndex, prevLogTerm, entries[], leaderCommit),并发发给各 Follower。 - Follower 做一致性检查:仅当本地在
prevLogIndex处的任期等于prevLogTerm时才追加新条目(保证前缀对齐);否则拒绝,Leader 减小nextIndex并回退重试。
日志复制:Leader 本地追加 → 并行 AppendEntries 落盘 → 多数派 ACK 后推进 commitIndex 并应用到状态机 → 后续心跳带上 leaderCommit 通知 Follower 提交。
5.2 commitIndex:基于 Quorum 的提交边界
日志何时能进状态机,既影响性能,也是安全底线:
一条预写日志,必须在 Leader 确认已落盘到多数派之后,才能标记为「已提交(Committed)」,再应用到状态机并向客户端返回成功。
Leader 用两组游标跟踪同步:
matchIndex[i]:Followeri已对齐并落盘的最高日志下标。nextIndex[i]:下次发给 Followeri的起始下标(失败则退,成功则进)。
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)**指网络把集群切成互不可达的孤岛,两侧若各自选主并写入,就会数据分叉。有了法定人数,共识自带隔离效果:
- 5 节点被切成
3 : 2时,只有含 3 个节点的多数派一侧能凑齐选票选出 Leader 并继续读写; - 2 节点少数派永远凑不出 3 票,选主走不通,写入入口关闭,自动变为不可用;
- 分区恢复后,少数派重新连上,被更高
term的 Leader 压制,对齐状态,并丢掉孤岛期产生的未提交脏日志。
因此,正确实现的共识集群不会长期双主并存——最坏是少数派保护性中断。许多自研协调件出问题,往往就是底层没把 Quorum 写严,分区后两侧各自为政。
7.2 奇数节点部署
容错能力由 f = ⌊(N-1)/2⌋ 决定(丧失 f 个节点后仍能维持多数派):
| 节点基数 N | 法定人数 Quorum | 容错水位 f |
|---|---|---|
| 3 | 2 | 1 |
| 4 | 3 | 1 |
| 5 | 3 | 2 |
| 6 | 4 | 2 |
规律很清楚:从 3 扩到 4、从 5 扩到 6,容错水位不变,却多了机器成本与故障面。偶数节点收益差。更糟的是,偶数集群遇到 N/2 : N/2 均等分区时,两侧都凑不齐多数派,全局停写(奇数节点任意一刀切,总有一侧仍占多数)。规划共识集群时,用奇数节点(3、5、7),是在给定成本下同时兼顾容错与脑裂防御的常规选择。
8. 共识架构的工程落地与选型
对业务团队来说,多数情况下不必自研底层共识引擎。成员变更、快照压缩、WAL 管理、网络重传等细节,成熟开源组件已经趟过。
- etcd:Kubernetes 控制面依赖它,底层是 Raft(
etcd-io/raft也被大量外部系统复用)。提供强一致 KV、Watch 与租约(Lease)——做分布式锁、服务发现与选主时,它是常见底座。 - ZooKeeper:自研 ZAB(ZooKeeper Atomic Broadcast),设计与 Multi-Paxos / Raft 同属一路(单 Leader + Quorum + 顺序广播)。ZAB 与 Raft 的差异、一次性 Watch、会话管理,放到本系列第 2 篇细讲。
- Consul:底层用 HashiCorp 的 Raft,常见于服务注册与 KV。
- Kafka KRaft:早期 Kafka 依赖 ZooKeeper 管元数据(Controller 选主、Topic / 分区路由);**KRaft(Kafka Raft)**把元数据做成基于 Raft 的内建 Topic,去掉外部 ZK 依赖,降低运维复杂度并抬高元数据规模上限。这与 Kafka 副本可靠性 里 ISR +
acks=all的「数据面多数派」是同一套思路在不同平面上的体现。 - NewSQL 存储层:TiKV、CockroachDB 的 Multi-Raft(分片成细粒度 Raft Group),以及 Google Spanner 基于 Paxos 的全球强一致,都是把共识沉到分片调度层。
选型建议:元数据、核心配置、选主、分布式锁 → 优先 etcd 或 ZooKeeper;新建流处理枢纽 → 可直接用 Kafka KRaft;业务代码里不要自行封装简易共识,把这类能力交给经过生产验证的组件。
9. 强一致性与最终一致性的架构取舍
共识给出强一致性,代价是更高延迟与更严的分区行为。架构上要和最终一致性划清边界,避免把共识用到不该用的地方:
| 对比维度 | 共识(Raft / Paxos,强一致 / CP) | 最终一致(Dynamo 风格,AP) |
|---|---|---|
| 一致性承诺 | 线性一致性(Linearizability):写入对全局可见,单一时序 | 最终一致性(Eventual):允许短暂不一致,后台收敛 |
| 写入路径 | 单次写入须经法定多数派确认,延迟更高 | 可向少数副本甚至本地先 ACK,延迟更低 |
| 网络分区 | 切断少数派写入(用局部不可用保全局一致) | 允许两侧继续写,事后用向量时钟或 LWW 调和 |
| 典型场景 | 元数据、控制器选主、分布式锁、核心配置、金融对账 | 购物车、点赞计数、弱时效会话、离线分析缓存 |
| 代表技术 | etcd / ZooKeeper / Google Spanner | Cassandra / DynamoDB / Riak |
一句话:共识把「严格时序与唯一事实」做成硬约束,代价是分区期可用性与写入延迟;最终一致性接受短暂不一致,换更高可用与更低延迟。 微服务里二者常并存——共识管小体积核心元数据,最终一致承接海量边缘数据。业务侧冲突调和、幂等与去重,见 幂等与一致性探微。
参考
- Diego Ongaro, John Ousterhout. In Search of an Understandable Consensus Algorithm (Raft 论文):Raft 论文原文,选举 / 日志复制 / 安全性 / 成员变更的一手材料。
- The Raft Consensus Algorithm(含可视化动画):Raft 官方站点,交互式演示选举与复制,适合配合本文 §4 / §5。
- Leslie Lamport. Paxos Made Simple:Lamport 用更短篇幅讲清 Paxos,适合对照 §3 两阶段。
- Leslie Lamport. The Part-Time Parliament:Paxos 原始奠基论文。
- Fischer, Lynch, Paterson. Impossibility of Distributed Consensus with One Faulty Process (FLP):FLP 不可能定理原典,对应 §2.2。
- etcd Raft 库与文档:工业级 Raft 落地文档,配合 §8 阅读。