分布式理论 - CAP
分布式理论 - CAP
CAP 是分布式系统里被引用最多、也最常被误用的理论。它讨论的不是「三选二」的菜单,而是一个否定性结论:在网络分区存在的前提下,分布式系统无法同时做到「线性一致」和「每个请求都有响应」。本文偏理论与证明直觉,工程侧的取舍见 CAP 与 BASE。
1. 从猜想到定理
- 2000 年,Eric Brewer 在 PODC 的主题演讲中提出猜想:一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)三者不可兼得,最多满足其二。
- 2002 年,Seth Gilbert 与 Nancy Lynch 给出了形式化证明,把这个成立于直觉的猜想升级为定理(常被称作 Brewer 猜想或 CAP 定理)。
- 2012 年,Brewer 又撰文《CAP Twelve Years Later: How the "Rules" Have Changed》,亲自澄清「三选二」的表述具有误导性,主张按分区事件、按数据粒度去谈取舍。
所以今天读 CAP,正确的姿势是:把它当作一条关于「分区期间」的边界结论,而不是一张固定的选型菜单。
2. 三个性质的严格定义
| 字母 | 名称 | 精确定义(要点) |
|---|---|---|
| C | 一致性 Consistency | 等价于线性一致性:所有操作仿佛在单一时间点上原子地发生,读到的总是最近一次已完成的写 |
| A | 可用性 Availability | 每个到达非故障节点的请求,都能在有限时间内得到非错误的响应(不保证是最新数据) |
| P | 分区容错性 Partition tolerance | 网络可能丢失任意多的消息、节点之间可能被割裂,系统仍能继续运行 |
两个容易踩的坑:
- A 不等于「高性能」或「不宕机」。它只要求「活着且能应答」,应答哪怕是旧数据也算数;响应很慢但最终回来了,在定义上仍算可用。
- C 不是「任何程度的同步」。CAP 里的 C 特指最强的那一级——线性一致,而非最终一致。把最终一致也当成 C,就会得出「CAP 不成立」的错误结论。
还要特别注意 P 不是可选项:真实网络里分区一定会发生(交换机故障、跨机房链路抖动、长时间 GC 停顿都会让节点间「失联」)。既然 P 不能放弃,真正的抉择就落在 C 与 A 身上。
3. 为什么不可兼得:证明直觉
证明的直觉可以浓缩成一个两分区的思想实验。把系统在逻辑上切成两半 G1、G2,它们之间的消息可以被无限延迟(这正是 P 的含义)。
- 客户端向 G1 写入新值
v1。为了让这个写「成功」,G1 返回了成功响应。 - 另一个客户端向 G2 发起读请求,而 G2 收不到来自 G1 的任何消息。
- 此时 G2 只有两种选择:
- 等,直到收到 G1 的同步——但在 G2 看来,G1 的消息可能永远不来,它无法确定要等多久,也就无法在有限时间内应答,违反了 A;
- 不等,直接返回自己手上的旧值——那么读到的是一个已经完成的写的「过去」,违反了 C。
无论怎么选,都至少丢掉一条。因此结论是:当分区发生(P 成立)时,无法同时保证 C 与 A。 这就是 CAP 定理的正确表述,而不是笼统的「三选二」。形式化证明在 Gilbert 与 Lynch 的论文里使用了「异步网络模型」:模型本身不允许用「等一个超时」来区分「消息永远丢了」和「消息只是很慢」,因此上面第 3 步的「等」无法给出有限时间的界。
关键限定词:分区期间
上述矛盾只在分区存在时成立。若网络一切正常,节点之间能正常通信,系统完全可以同时提供一致性和可用性——这也正是「三选二」说法最致命的误导。给定 P 无法放弃,真正的抉择其实是:在分区的那段时间里,你要 C 还是要 A。
4. 「三选二」为什么会误导
把 CAP 当成固定菜单会带来三个错误推论:
- 误以为平时也要在 C/A 之间牺牲一个。事实是分区不发生时,二者可兼得;取舍是分区事件驱动的,随时间而变。绝大多数时间系统并不处于分区,此时谈「牺牲」毫无意义。
- 误以为取舍是系统级、一刀切的。实际上同一个系统里,可以按数据、按操作、甚至按用户做不同选择:账户余额走 CP,商品浏览量走 AP。
- 误以为三者是非黑即白的。它们都能分程度:可用性在 0%~100% 之间连续变化;一致性有一整条谱系;连「是否处于分区」都可以因节点而异(网络的一半认为断开了,另一半未必)。
Brewer 本人的建议是:既然是分区事件触发的取舍,那么设计重点应放在分区恢复后如何补偿——记录分区期间发生的冲突,在通信恢复后加以合并/修复,而不是纠结于「永久性地二选一」。
5. 一致性是一条谱系
CAP 里的 C 是最强的一级,但工程上更强的一致性意味着更高的延迟与更低的可用性。由强到弱大致是:
| 级别 | 含义 |
|---|---|
| 线性一致(Linearizable) | 操作看起来在单一时间点原子发生,全局有序 |
| 顺序一致(Sequential) | 所有进程看到同一操作顺序,但不要求与真实时间对齐 |
| 因果一致(Causal) | 有因果关系的操作顺序被保留,无因果的可乱序 |
| 最终一致(Eventual) | 停止写入后,各副本最终收敛到同一值 |
因此正确的问法不是「要不要强一致」,而是「这份数据需要哪一级一致性」。很多所谓的「CAP 之争」,本质是同一系统里不同数据被套用了不同的一致性等级。
6. 常见系统的倾向
把 CAP 落到具体系统,可对照如下(仅表示典型倾向,实际往往可配置):
| 系统 / 组件 | 倾向 | 说明 |
|---|---|---|
| ZooKeeper / etcd | CP | 协调、选主、配置,分区时可能短暂不可用以保元数据一致 |
| 传统关系库强同步主从 | CP | 主库确认 + 从库同步,牺牲部分可用性换取不丢数据 |
| Eureka(注册中心) | AP | 宁可返回可能过期的服务列表,也不让注册中心不可用 |
| Cassandra / DynamoDB | AP(可调) | 通过一致性级别在 C/A 之间滑动 |
| DNS | AP | 最终一致可接受,可用性优先 |
7. 对 CAP 的补充:PACELC
CAP 只刻画了「分区时」的取舍。分区没有发生时呢?系统依然要在延迟(Latency)与一致性之间权衡:要强一致就得多等若干副本确认,延迟升高;要低延迟就可能读到暂时落后的副本。这被总结为 PACELC:
如果有 P(分区),在 A 和 C 之间选;否则(Else),在 L(延迟)和 C 之间选。
它提醒我们:即使网络完全健康,强一致也不是免费的,代价体现在延迟而非「能不能用」上。一个常被忽略的事实是,多数系统的 SLA 里延迟与可用性同样关键,PACELC 比 CAP 更贴近日常工程。
8. 从理论到工程
CAP 的价值在于提供一把「取舍的标尺」,而不是一张选型表。如何把 C/A/P 映射到具体系统,以及 BASE 如何作为强一致的工程替代,见另一篇偏工程视角的 CAP 与 BASE。
一句话把握全局:CAP 说的是「分区时 C 与 A 二选一」,BASE 说的是「日常可以退到最终一致」,PACELC 说的是「没分区时也在用延迟换一致性」。
9. 小结
- CAP 是定理:分区发生时,无法同时满足线性一致与可用性。
- 正确的表述是「P 必选,分区期间在 C 与 A 之间二选一」,不是三选二。
- 取舍是分区事件驱动的、可按数据细分的、且可以度量程度的。
- 一致性是一整条谱系,选型的关键是判断「这份数据需要哪一级」。
- PACELC 补上了「无分区时在延迟与一致性之间权衡」这一维度。
- 设计重点应是分区恢复后的冲突补偿,而非永久性的非此即彼。