21.1 共识问题定义

一句话

共识是让 N 个可能崩溃的进程对“一个值”达成不可反悔的一致:一致性(不会有两个决定)、有效性(决定的值有人提过)、终止性(非故障进程最终都决定)——三条缺一不可,而 FLP 说明异步网络里终止性无法保证。

三个性质:少一条都不叫共识

性质 形式化说法 违反会怎样
一致性(Agreement) 任意两个正确的进程不会决定不同的值 双主、脑裂、两个客户端看到不同的“成功”
有效性(Validity) 被决定的值必须是某个进程提议过的,不能凭空产生 返回一个没人提过的值(如默认 0),业务语义错乱
终止性(Termination) 每个正确的进程最终都会决定一个值 系统卡住、无限等待,等于不可用

有时还会加一条完整性/稳定性:一旦决定就不能反悔(不可撤销)。这条实际上是“决定”一词的定义,工程上对应“已提交的日志不能改”。

🔑 三条性质里,终止性是最难的一条:一致性与有效性可以在“永远不决定”时平凡地满足。共识的全部工程难度,都花在“如何在故障下仍然终止”上。

从“决定一个值”到“复制一条日志”

单值共识看起来没用(决定一次就结束了),但它是所有复制系统的内核:

单值共识:决定哪个提案成为 v(Paxos 的经典形式)
   ↓ 每个日志位置做一次
多值共识(Multi-Paxos / Raft):决定位置 1、2、3… 上分别是什么
   ↓ 按顺序执行
状态机复制(SMR):所有副本执行相同的日志 → 状态一致

这就是为什么“共识算法”等于“复制日志算法”:日志里的每个位置,都是一次单值共识。

用途 共识在决定什么
选主 哪个节点是 leader(任期号 + 候选人;工程实现见 48.4 Leader 选举)
配置管理 集群成员、分片归属
分布式锁(强安全) 谁是当前持有者(互斥问题本身见 20.1 互斥问题与临界资源 (安全性 / 活性))
数据库复制 每条事务在日志中的位置与顺序
分布式事务协调 提交还是回滚

为什么难:三个绕不过去的约束

约束 含义 后果
没有共享内存 只能靠消息 无法用一条 CAS 指令定序
消息延迟无上界 异步模型里消息可以任意晚 无法区分“对方慢”还是“对方死”(35.4 故障检测)
进程可能崩溃 决定前就没了 必须靠多数派,而不是“问所有人”

FLP 不可能性(22.2 FLP 不可能性)把第三条推到极致:在完全异步、且允许一个进程崩溃的模型里,不存在既保证一致性又保证终止性的确定性共识算法。工程上的解法不是“违反 FLP”,而是加假设:

  • 部分同步:假设网络最终会恢复“足够快”,用超时触发选举(Raft/Paxos 的工程实现都这么干);
  • 随机化:用随机退避打破对称,终止性变成“以概率 1 终止”;
  • 故障检测器:用 φ accrual 之类给出“可疑度”,而不是二值判断。

💡 所以“Raft 保证了共识”这句话要补全:在部分同步假设 + 多数派存活的前提下,Raft 保证一致性与最终终止。网络一直抖,它就会一直选主、一直不提交——这不违反 FLP,因为 FLP 说的是异步模型。

需要多少副本:2f+1 与 3f+1

故障类型 最少副本 quorum 直觉
崩溃故障(crash) 2f + 1 f + 1 任意两个多数派必相交(推导见 21.2 Quorum 与 W + R > N 的证明)
拜占庭故障(byzantine) 3f + 1 2f + 1 恶意节点可能“两边都说谎”,交集里不能有它(见 22.1 故障类型)
f(能容忍几个坏) 崩溃模型副本数 拜占庭模型副本数
1 3 4
2 5 7
3 7 10

成本:消息复杂度通常是 O(N²)(每个节点要和所有节点通信),N 从 3 涨到 7,消息量涨 5 倍多;跨城部署时还要乘以 RTT。这就是为什么共识集群通常只有 3 或 5 个节点,而不是 21 个。

共识不是万能的:它不解决什么

共识能解决 共识不解决
多个副本对“顺序”达成一致 值本身对不对(有效性只要求“有人提过”)
故障时仍只有一个真相 吞吐与延迟(单 leader 串行提交,见 21.12)
选主、配置、日志复制 业务冲突(两个都合法的写,仍需合并规则)
强一致的元数据 跨地域低延迟(跨城一次决策至少 1 个 RTT)

常见误解

误解 现实
“共识 = 选主” 选主只是共识的一个应用;日志复制、配置变更都要共识
“Paxos 决定一个值,所以没用” 多值共识 = 每个日志位置跑一次单值共识,Raft 是它的工程化
“多数派写入就线性一致了” 多数派只保证“不会有两个决定”;读还要 ReadIndex/租约(见 21.12)
“共识能解决分区” 分区时少数派只能不可用(17.1),如何取舍见 17.3 分区期间的取舍;共识只是让多数派继续工作
“加节点更可靠” 崩溃模型 3→5 节点能多容忍 1 个故障,但延迟与消息量都上升;跨城还受 RTT 限制
“FLP 说明共识不可实现” FLP 说的是异步 + 确定性 + 终止性三者不可兼得;加部分同步/随机化即可工程实现

反模式

反模式 后果
用“两阶段提交 + 超时”代替共识 协调者挂掉后悬挂,参与者长期锁资源
3 节点集群跨 3 个城市 每次提交 30ms 起,吞吐掉一个数量级;且任两地网络抖动就选不出主
用 gossip 最终一致冒充共识 分区时会出现两个“真相”
忽略有效性,返回默认值 客户端把“没人提过的值”当真相
只测正常路径,不测分区/崩溃 真故障时才发现选主逻辑有 bug
把共识用于高频数据 单 key 串行提交上限 = 1/RTT(见 21.12)

自检

Q1 共识的三条性质里,哪一条最难?为什么另外两条可以在“什么都不做”的情况下被平凡满足?

▸ 答案:终止性最难。一致性和有效性都是“如果有决定,则……”形式的条件:一个永远不做决定的算法,不会产生两个不同的决定(一致性成立),也不会决定没人提过的值(有效性成立)。所以只满足这两条毫无价值——“永远不决定”在业务上等于不可用。终止性要求“在故障下仍然做出决定”,这才是共识的全部难度,也正是 FLP 打击的那一条。

Q2 3 个节点、容忍 1 个崩溃,为什么是 3 而不是 2?用 quorum 相交解释。

▸ 答案:选主/提交需要多数派 f+1 = 2 票。2 个节点时多数派是 2,任意一个挂掉就凑不齐 2 票,系统不可用;3 个节点时多数派是 2,挂 1 个还剩 2 票,仍能工作。更本质的理由是任意两个多数派必须相交:2q > N,q=2、N=3 满足 4 > 3,所以新主一定能看到旧主已提交的日志。N=2、q=2 时 4 > 2 也相交,但容错为 0(任一节点挂即不可用),等于没有容错。

Q3 为什么说“多数派写入 = 线性一致”是错的?

▸ 答案:多数派写入只保证写不会丢失、不会出现两个互相矛盾的提交(Agreement),不保证读能看到最新值:从库/跟随者可能还没应用该日志;即使从 leader 读,如果读不加 ReadIndex/租约,也可能读到旧状态(比如刚选出的新 leader 还没提交 no-op)。线性一致的读需要额外机制:ReadIndex(一次多数派往返确认自己仍是 leader)、租约读(依赖时钟假设)或把读也走一遍日志。

Q4 FLP 说异步网络里共识不可能,为什么 Raft/etcd 还能用?它们“违反”了 FLP 吗?

▸ 答案:没有违反,它们改变了假设。FLP 的前提是“完全异步 + 确定性算法 + 必须终止”;工程实现加了两条:① 部分同步(假设网络最终会稳定一段时间),用选举超时触发选主;② 随机化(随机选举超时),把“保证终止”换成“以概率 1 终止”。在假设不成立时(网络长期抖动),系统会退化为“不提交”,这与 FLP 的结论一致——实际系统用“不可用”换取了一致性。

关联