17.2 为什么 “三选二” 是错的

一句话

“三选二”把三个不同层级的东西摆上了同一张菜单:P 是网络给的事实,不是选项;C 与 A 不是布尔按钮;取舍也不是一次性全局决定——真实系统是在“分区时 / 平时、这个操作 / 那个操作”上分别下注。

错在五处,一张表先看完

# “三选二”的说法 现实 正确说法
1 P 是可选项,可以选 CA 只要有跨节点复制,分区就会发生;选“不要 P”等于不做故障处理 P 是前提,只能选分区时 C 还是 A
2 三选二是全局的、一次性的 同一个系统里,支付与点赞可以选不同策略;分区时与平时也不同 取舍是 per-operation / per-record / per-time 的
3 C 与 A 都是布尔值 A 有 yield(回答问题比例)、C 有一整个谱系(16.7 一致性模型谱系图) C/A 都是连续量,且可以只降级一部分
4 只有 C、A、P 三个维度 日常最常付的代价是延迟,CAP 完全没提 补上 L,见 17.4 PACELC
5 分区是一种永久状态 分区通常持续秒级到分钟级,之后系统要恢复 还要回答“分区结束后怎么收拾”(见 17.3 分区期间的取舍)

错法一:P 不是选项,而是前提

CAP 的 P 说的是“节点之间的消息可以任意丢失”(准确表述与证明见 17.1 CAP 的由来与准确表述)。它不是你能“选择不要”的性质,而是网络的物理事实:

你以为的“故障” 真实的分区来源
服务器宕机 机房之间光纤被挖断、云厂商 AZ 间网络抖动
进程崩溃 交换机 ACL 配错、MTU 不一致导致大包被丢
磁盘坏掉 进程活着但 GC 停顿 5 秒,心跳全部超时
断电 跨地域链路拥塞,RTT 从 30ms 涨到 3 秒

🔑 “我们选 CA”这句话,翻译过来只有三种可能:① 我们是单机;② 我们没做故障检测(分区时要么全站不可写,要么出现双主);③ 我们在撒谎(把“平时很快”说成了“分区时可 C 也可 A”)。

三个自称“CA”的系统,逐个拆

系统 它真正的性质 分区时会发生什么
单机 MySQL 没有副本,谈不上 C/A 取舍 机器坏 = 全挂;客户端网络断 = 对它不可用
主从异步复制(读写分离) 写走主库、读走从库,从库可能读到旧值 主库不可达 → 写不可用;主从断连 → 从库继续返回旧数据(C 也不满足)。它既不是 C 也不是 A,只是“平时快”
Redis 主从 + Sentinel 异步复制,故障切换可能丢最后一批写 分区时两边都可能接受写;切换后旧主的数据被丢弃(丢写),且客户端可能短暂双写

错法二:取舍是“按操作、按数据、按时间”的

这是 Brewer 2012 年那篇回顾里最实用的一条:CAP 的选择不需要全局统一。

同一个电商系统里:
  商品浏览数     → AP(少一次 +1 没人发现,合并时求和即可)
  购物车         → AP(两侧都能加,合并取并集,冲突让用户选)
  优惠券库存     → CP(超发要赔钱,宁可下单失败)
  账户余额       → CP(不许透支,必须协调)

同一个系统,四个子系统,四种取舍——“我们这个系统是 AP 的”这种说法,信息量为零。

“按时间”指的是分区模式:正常模式下两边都服务;检测到分区后,进入分区模式——记录哪些不变式被违反了、哪些操作要降级,恢复后再补。Brewer 把这个叫“partition mode + recovery”,它比“选一个字母”具体得多:

阶段 要做的事
正常 全功能,尽量降低协调成本
检测到分区 进入降级模式:少数派拒写 / 只读 / 排队;记录被违反的不变式
分区恢复 反熵、冲突合并、对账、补偿、告警复盘

错法三:C 与 A 都不是布尔值

A 不是布尔:CAP 的 A 要求“每个请求都返回”。现实系统全都有超时,按定义全都不满足 A——这就是 Kleppmann 说定理“近乎真空”的原因。工程上真正可度量的是:

yield   = 被回答的请求 / 总请求
harvest = 回答中包含的数据 / 应有的数据

一个搜索集群在故障时返回了 90% 的结果(harvest = 0.9)、回答了 100% 的请求(yield = 1.0)——它既“可用”又“不完整”,这比“A 还是 C”精确得多。

C 也不是布尔,而是一个谱系:

模型 分区期间能否两侧都读写 读到的可能是
线性一致 不能(少数派必须拒) 最新值
顺序一致 不能 某个全局序上的值,可能不是最新
因果一致 能 因果上合法的值(可能是旧的,但不违因果)
最终一致 能 任意旧值,直到收敛

💡 这张表的完整版与判定方法在 16.7 一致性模型谱系图;“分区期间哪一列可用”的推导在 17.3 分区期间的取舍。

线性一致到底要求什么、怎么判定,见 16.2 线性一致性。

错法四:漏掉了延迟这个每天都在付的账

CAP 只谈“分区发生时”。可分区是偶发的,协调成本是每天的:

  • 跨城强一致写:多等 1 个 RTT(北京–上海 30ms);
  • 跨洲强一致写:多等 1 个 RTT(中美 180ms);
  • 全球部署还要加 TrueTime 的提交等待(ε 量级 7ms,见 17.4 PACELC)。

这些成本在“没分区”的 99.9% 时间里照样要付。Abadi 2012 年因此提出 PACELC:P 时选 A/C,否则(Else)选 L/C。CAP 只回答了前半句。另一个常被并列引用的结论是 FLP——它约束的是终止性而不是可用性,见 22.2 FLP 不可能性。

错法五:把分区当永久状态,忘了恢复

“三选二”暗示选完就结束了。真实系统在分区结束后必须回答:两侧写的数据怎么合?被违反的不变式怎么补?

合并策略 能解决什么 会丢什么
LWW(最后写入者赢) 实现最简单 必丢一条:两条并发写,时间戳小的被静默覆盖
版本向量 能检测出并发冲突 只检测,不解决;要业务规则或人工
CRDT 无冲突合并(计数器、集合、寄存器) 只适用于满足交换律/幂等的数据结构
业务规则 按业务语义挑赢家(如“库存以少为准”) 需要每个业务单独设计
对账 / 人工 兜底,能处理资金类 成本高、有延迟

那正确的说法是什么

场景 别说 应该说
选数据库 “它是 AP 的” “它在分区时允许两侧写,冲突用 LWW 合并;平时读本地,延迟 2ms,陈旧窗口 p99 约 200ms(示例数字,按实测填)”
设计账户系统 “我们选 CP” “余额扣减走多数派;分区时少数派返回 503,客户端可重试;恢复后对账”
评审架构 “CAP 三选二,我们选了 CA” “我们没有跨节点复制 / 我们有脑裂风险 / 我们的故障检测超时是 3 秒”
解释线上故障 “因为 CAP” “因为分区期间两侧都接受写,恢复后 LWW 把 6000 条写里的 3000 条覆盖了”(见 17.3 分区期间的取舍)

反模式

反模式 后果
把“CA”写进架构文档 掩盖了没有故障检测的事实,分区时脑裂
用“三选二”解释业务取舍 一句话盖住四种不同策略,评审时无法追问
认为“AP 系统就不用管一致性” AP 只是允许分区期间写,收敛与冲突解决仍然要做
拿 CAP 的 A 去论证 SLO 两套定义混用,结论不成立
只在故障复盘时提 CAP 平时该算的延迟账(PACELC)一次没算
把分区当成“不会发生” 第一次网络抖动就丢数据、双写、超卖

自检

Q1 同事说“我们这个服务选的是 CA,因为我们在同城两个机房做了同步复制”。这句话里至少有两处问题,请指出。

▸ 答案:① “CA”不成立:同城两机房同步复制意味着有跨节点通信,就存在分区(两个机房之间的链路会断、会抖),P 是前提不是选项;同步复制在分区时要么阻塞等待(放弃 A),要么降级(放弃 C)。② “选了”不成立:取舍不是全局一次性的,同城 OR 跨城、写 OR 读、余额 OR 浏览数,策略可以不同。正确的描述是:“跨机房同步复制,分区时少数派拒写,写延迟增加约 1 个同城 RTT(1–3ms)”。

Q2 为什么说“带 1 秒超时的系统,在 CAP 意义上不可用”?这是否意味着 CAP 没有实用价值?

▸ 答案:CAP 的 A 要求每个请求得到响应(操作完成并返回结果),超时意味着这次操作没有完成,所以按定义不可用。这不代表 CAP 没用,而是说明它的 A 是一个理论化的极强条件,直接拿来衡量工程可用性会得出荒谬结论。它的实用价值在于:逼你回答“分区时少数派怎么办、恢复后怎么合”,而不是给你一个可以贴在产品上的字母组合。

Q3 一个电商系统把“商品浏览数”和“优惠券库存”都做成了 AP(分区两侧都能写)。分区恢复后会发生什么?分别该用什么合并策略?

▸ 答案:浏览数用 AP 没问题:两侧各自加一,恢复后求和合并即可(计数满足交换律与幂等,属于 CRDT 里的计数器),最多短暂不一致,不会出错。优惠券库存用 AP 就是事故:两侧各卖 100 张、总库存 100,恢复后超发 100 张,而且没有任何合并策略能把已经发出去的券收回来——只能赔偿。库存这类不满足 I-confluence 的不变式(见 17.6 CAP 之后的补充理论)必须协调:分区时少数派拒写,或者用“预扣 + 对账”把风险限制在可补偿范围内。

Q4 “三选二”这种说法为什么在教学中流行,却在工程上危险?

▸ 答案:它流行是因为压缩比高——三个字母、一个“选两个”的规则,就能让人快速记住“分布式系统有取舍”。危险在于它把三个不同层级的东西(前提、性质、连续量)压成了同一层,导致两个坏习惯:用标签代替分析(“我们是 AP,所以不用管一致性”),用一次决定代替分场景设计(不区分读写、不区分业务、不设计分区模式与恢复)。可以用它做入门钩子,但做架构决策时必须展开成“分区时少数派做什么、平时付多少延迟、恢复后怎么合”这三问。

关联