CAP 十二年后 — Brewer 自己承认"三选二"是误读
是什么
2000 年 Brewer 提出 CAP 猜想:分布式系统的一致性(C)、可用性(A)、分区容忍(P)只能三选二。十二年后,他自己写了这篇文章说:这个”三选二”传太广,但其实是误读。
日常类比:考驾照时教练说”油门和刹车不能同时踩”。新人记成”开车永远只能踩一个”。教练后来纠正——只有车快撞墙那一瞬间你才要在油门刹车之间二选一;平时你两个都不踩、或者轻轻调,都没问题。
CAP 也是这样。分区罕见,平时 C 和 A 你都能拿到。只有真发生网络分区那几秒,你才面临 C 还是 A 的取舍。
为什么重要
不读这篇会有四个误解一直跟着你:
- 以为做分布式必须永远放弃 C 或 A → 其实只在分区瞬间放弃
- 以为选了 AP 就完全不一致 → 其实能按操作、按不变量精细放松
- 以为 P 可选 → 真实网络里 P 必然存在,所以本质是 C 和 A 在分区下的取舍
- 以为 CAP 涵盖了所有权衡 → 没分区时还有”延迟 vs 一致”,需要 PACELC 补
这是一篇作者亲自给自己的理论打补丁的文章,价值在于”作者承认哪里被误读”。
核心要点
1. 三选二是简化、不是真相
原版 CAP 在没分区时其实可以同时给 C 和 A。只在分区瞬间才面临选择。
2. C 和 A 都是连续谱
不是布尔。可以按操作分类(取款限额、转账要强一致),可以按不变量分类(“余额≥0”必须保,“积分排行榜”可以晚几秒)。
3. 分区处理三阶段框架
Brewer 提出处理分区的工程模板:
- 检测:判断分区开始(通常用超时)
- 进入分区模式:限制部分操作以保关键不变量;记录”分区期间发生了什么”
- 恢复 + 补偿:分区结束后协调状态、对违反的不变量做补偿(罚款、回滚、人工介入)
4. 延迟 = 局部分区
“超过 timeout 的等待,效果等同于断开”。这把延迟和分区统一了——你设的 timeout 越短,触发”分区模式”越频繁。
这一句很关键:原版 CAP 把”分区”当成黑白二态。Brewer 这里改口——分区是一段连续光谱,由你定义的 timeout 来切。
5. 不变量是一等公民
文章反复强调:先列你的系统有哪些不变量,再决定哪些必须强一致、哪些能补偿。
例如银行系统的不变量:
- “余额≥0”(强一致 / 或事后追账补偿)
- “总账面 = 入账 - 出账”(必须最终一致)
- “用户能登录”(可用性优先)
不同不变量对应不同的 C/A 取舍,不是整个系统统一一档。
实践案例
案例 1:ATM 取款(30 年的工业实践)
- 银行 ATM 设计选 A 优先:分区时仍允许取款
- 为什么:手续费收益 > 透支损失
- 怎么限风险:分区时限额(如每天最多 200 美元)
- 分区结束怎么办:透支由银行追账、罚款补偿——这就是”事后 compensation”
Brewer 用 ATM 说明:工业界其实早就在用三阶段框架了,只是没人写出来。
案例 2:电商购物车
- 分区时两个数据中心各自接收”加入购物车”操作 → 选 A
- 分区结束合并:取并集(不变量是”加进去的不能丢”,重复加一次的代价小)
- Amazon Dynamo 论文里就是这套
案例 3:Google Spanner 反向操作
- 用 GPS + 原子钟把”不确定的时间窗口”压到几毫秒
- 等价于把”分区可能性窗口”压到极小
- 让系统逼近 CA——不是违反 CAP,而是把 P 出现概率压到可忽略
案例 4:Yahoo PNUTS 跨地域
- 同一份数据多地副本,不同地域的应用就近读
- 用 “record-level mastership”:每条记录指定一个 master,写必须去 master,读可读本地
- 这样大部分操作走 L 优先(低延迟弱一致),少量关键操作走 C 优先
PACELC:CAP 没说完的那一半
同一期 IEEE Computer 上,Daniel Abadi 发了 PACELC:
- Partition 时:在 A 和 C 之间选(这是经典 CAP)
- Else(没分区时):在 Latency 和 C 之间选
为什么需要:99.9% 的时间没分区,但你仍要在”等所有副本同步(高一致 + 高延迟)“和”立刻返回(低延迟 + 弱一致)“之间选。CAP 没覆盖这一面,PACELC 补上。
举例:
- DynamoDB 默认 PA/EL(分区时 A,平时 L)
- Spanner 是 PC/EC(永远选一致,靠原子钟把代价压低)
- MongoDB 默认 PA/EC(分区时 A,平时 C)
踩过的坑
- 误以为三阶段框架是算法 → 它是设计指南。怎么检测分区、怎么补偿,仍要按业务定。
- 误以为补偿可以无脑加 → 有些不变量(余额、库存)能补偿,有些(已发出的邮件)不能。设计时要先分类。
- 误以为 PACELC 取代 CAP → 它是补丁不是替换。CAP 仍是讨论分区时取舍的标准词汇。
- timeout 调太短 → 触发分区模式过于频繁,系统看起来”经常切换状态”,体验崩。
适用 vs 不适用场景
适用:
- 设计跨数据中心的服务时,先列不变量、再按 P/E 双场景定策略
- 评估开源数据库选型(看它的 PACELC 标签)
- 解读”为什么 X 系统选了看起来奇怪的一致性级别”
不适用:
- 单机系统(没有分区)
- 需要拜占庭容错的场景(CAP 假设节点不撒谎,BFT 要更强模型)
- 实时控制系统(毫秒级硬延迟,CAP 谈的是秒级以上的一致性窗口)
历史小故事(可跳过)
- 2000 年:Brewer 在 PODC keynote 上抛出 CAP 猜想——没证明,只是工程直觉
- 2002 年:MIT 的 Gilbert 和 Lynch 形式化证明,CAP 从猜想升级为定理
- 2008 年前后:NoSQL 大爆炸,“CAP = 三选二” 被广泛误用作”必须放弃 C”的借口
- 2012 年:Brewer 受不了,写这篇澄清;同期 Abadi 发 PACELC
Brewer 自己说:“我提出 CAP 是为了让大家开始讨论权衡,不是终结讨论。“
学到什么
- 理论的简化版传得最广——但作者本人通常嫌它过于简化
- 罕见情况决定架构:分区罕见,但你的设计 80% 是为了应付那 1% 的分区
- 不变量分类:先列你的系统有哪些不变量、哪些能补偿、哪些不能,再谈一致性级别
- 延迟和分区是一回事:timeout 就是”我宣布分区开始了”
延伸阅读
- 原文 PDF:Brewer 2012 IEEE Computer
- 同期 PACELC:Abadi, “Consistency Tradeoffs in Modern Distributed Database System Design”, IEEE Computer 45(2), 2012
- 形式化证明:Gilbert & Lynch, SIGACT News 33(2), 2002
- gilbert-lynch-cap — 2002 把猜想证成定理
- spanner-2012 — 用原子钟逼近 CA
关联
- gilbert-lynch-cap — 2002 形式化证明,把 CAP 从猜想升级为定理
- spanner-2012 — Google 用 TrueTime 把分区窗口压到毫秒级
- dynamo — Amazon 极致 AP 选型的代表作
- bigtable — Google 早期一致性强的存储,可与 Spanner 对照
反向链接
- dynamo —— Dynamo — 让购物车永远能写入的分布式存储
- spanner-2012 —— Spanner 2012 — 用原子钟和 GPS 给全球数据库发时间戳