17.1 CAP 的由来与准确表述
一句话
CAP 只在“发生分区”这一刻断言:一个既要求线性一致、又要求每个请求都必须返回的系统,在分区两侧同时服务时必然自相矛盾;P 不是可选项,真正的选择是“少数派闭嘴(CP)”还是“少数派说谎(AP)”。
三个字母,先把定义钉死
| 字母 | 严格定义(Gilbert & Lynch 2002) | 最常被误读成 |
|---|---|---|
| C(Consistency) | 原子一致 / 线性一致(16.2 线性一致性):所有操作存在一个与实时先后相容的全序,读返回该全序上最近的那次写 | ACID 里的 C(“余额不能为负”这类业务不变式) |
| A(Availability) | 每个到达非故障节点的请求都必须得到一个响应——不允许超时、不允许“稍后告诉你” | 99.99% 的年度可用率、SLO、可观测性面板 |
| P(Partition tolerance) | 节点之间的消息可以任意丢失,网络可能被切成两半 | 服务器宕机、进程崩溃、磁盘坏掉 |
🔑 三个定义里最容易被偷换的是 A:CAP 的“可用”是每个请求都要拿到回答。一个带 1 秒超时的系统,在 CAP 的意义上从来就不是“可用”的——它只是“工程上可用”。这个区别是 17.2 要拆的第一块砖。
由来:先有断言,后有证明,最后由作者自己修正
| 年份 | 事件 | 当时的主张 |
|---|---|---|
| 2000 | Brewer 在 PODC 主题演讲 Towards Robust Distributed Systems 中提出 | 一致、可用、分区容忍,三者不可兼得 |
| 2002 | Gilbert & Lynch 给出形式化模型并证明 | 在异步网络模型下,三个性质不能同时满足;并给出只满足其中两项的协议例子(CA 只在没有分区时才成立) |
| 2012 | Brewer《CAP Twelve Years Later: How the “Rules” Have Changed》 | “三选二”的表述有误导性:分区很少发生,取舍是按操作、按数据、按时间做的 |
| 2015 | Kleppmann《A Critique of the CAP Theorem》 | 定义太强,定理几乎“真空”;真正该看的是 yield/harvest、延迟与故障模式 |
💡 三个时间点要连起来读:CAP 是一条关于分区时刻的约束,不是一张给系统贴标签的选型表。提出者本人十二年后专门写文章纠正“三选二”,这件事本身就说明这个缩写有多容易被当成口号。
定理到底说了什么
证明只要三步(反证法)
分区前:副本 A 与 B 都已同步 x = 0
① 网络分区,{A} 与 {B} 互相不可达
② 客户端 1 连 A:W(x = 1) —— A 返回成功
③ 客户端 2 连 B:R(x) —— B 该返回什么?
假设系统既线性一致、又总是可用:
W 在 R 开始之前就已返回 → 线性一致要求 R 必须读到 1
但 B 与 A 分区,收不到 W → B 不知道 1 的存在
CAP 的 A 又要求 B 必须响应 → B 只能返回 0
1 ≠ 0,矛盾。
所以:分区时,B 要么不响应(放弃 A),要么返回旧值(放弃 C)。
这个证明没有用到时钟、没有用到超时、也没有假设节点会坏——它只用了两件事:消息会丢,以及两个分区都在服务。故障的完整分类(崩溃 / 遗漏 / 时序 / 拜占庭)见 22.1 故障类型 (崩溃 / 遗漏 / 时序 / 拜占庭)。
定理说了什么,没说什么
| 定理说了 | 定理没说 |
|---|---|
| 分区期间,C 与 A 不能同时满足 | 延迟、吞吐、成本、运维复杂度 |
| 只针对单个对象的读写(寄存器模型) | 事务、跨对象不变式、业务规则 |
| 网络可任意丢消息,且可能持续任意长 | 分区多久发生一次、平均持续多久 |
| 不要求节点知道“自己正处在分区里” | 分区恢复后怎么合并冲突 |
| 三个性质按“满足 / 不满足”二值判定 | 可用的连续度量(yield / harvest)、部分一致 |
⚠️ 这张表的右列,就是后来 PACELC、Harvest/Yield、HAT、CALM 要补的洞(见 17.6 CAP 之后的补充理论)。把 CAP 当“完整理论”用,会用错。
一致性的不同强度如何排列、各自要付什么协调成本,见 16.7 一致性模型谱系图。
为什么 P 在工程上不是选项
“P 是选项”这个错觉,来自把“分区”理解成“服务器坏了”。真正的分区是节点之间还能跑、但彼此收不到对方的消息:
- 机房之间的光纤被挖断、云厂商的 AZ 间网络抖动;
- 交换机 ACL 配错、MTU 不一致导致大包被丢;
- 进程活着但 GC 停顿 5 秒,心跳全部超时;
- 跨地域链路拥塞,RTT 从 30ms 涨到 3 秒——慢到像分区。
只要系统里有“跨节点复制”,P 就在那儿。所谓“CA 系统”,只可能是单机系统,或者根本没有副本(那它也就没有 CAP 要讨论的一致性问题)。按“假设分区不会发生”去设计,得到的不是 CA,而是脑裂(见 17.3 分区期间的取舍)。
分区有多常见?没有放之四海皆准的数字,但可以用量级算一笔账:
假设 1000 条跨节点链路,单链路年故障率 0.1%
→ 期望故障次数 = 1000 × 0.001 = 1 次/年
若每条链路只影响一对节点,全年“至少一次分区”的概率 ≈ 1 - e^(-1) ≈ 63%
(上面的 0.1% 是假设值,只用来示范“链路一多,分区就从黑天鹅变成例行事件”。真实值取决于机房、运营商与云厂商,且分区时长是重尾分布——多数几秒,少数几小时。)
“可用”的两种口径,别混着用
| 口径 | 定义 | 用在哪 |
|---|---|---|
| CAP 的 A | 每个请求必须在有限时间内得到回答(返回错误、让客户端重试都不算) | 理论分析、定理证明 |
| 工程可用性 | 成功请求 / 总请求,或“时间占比”(99.9%、99.99%) | SLO、告警、合同、复盘 |
| yield / harvest | yield = 被回答的请求比例;harvest = 回答里包含的数据比例 | 降级设计(见 17.6 CAP 之后的补充理论) |
三者可以同时成立,也可以互不相干:一个系统可以“99.99% 的时间在服务”,但每次网络抖动都让少数请求超时——它在工程上健康,在 CAP 意义上不可用。反过来,一个“每次请求都返回”的系统可以一直返回陈旧数据——它在 CAP 意义上可用,但完全不满足 C。
🔑 判断一个系统“是 CP 还是 AP”,不要看它的宣传语,要看分区发生时它对少数派做什么:拒写(CP)、照写(AP)、还是只读/排队(降级)。
而“为什么不能简单地三选二”,见 17.2 为什么 “三选二” 是错的。
反模式
| 反模式 | 后果 |
|---|---|
| 把 CAP 的 C 当成 ACID 的 C | 用 CAP 讨论“余额不能为负”,讨论的是两个不同的问题 |
| 把 CAP 的 A 当成“99.99% 可用” | 用 SLO 数据证明“我们满足 CAP 的 A”,结论无效 |
| 声称系统是 CA | 要么是单机,要么没做故障检测,分区时直接脑裂 |
| 用 CAP 给产品贴标签选型 | 忽略了延迟、运维成本、数据模型,选出来的系统解决不了真问题 |
| 认为“P 可以不要” | 不做分区处理,第一次网络抖动就丢数据 |
| 认为 CAP 证明了“强一致不可扩展” | 定理只谈分区时刻的 C/A,不谈吞吐与延迟 |
自检
Q1 有人说“我们的服务年度可用性 99.99%,所以满足 CAP 的 A”。这句话错在哪?请用定义说清。
▸ 答案:CAP 的 A 是每个请求都必须得到响应(不允许超时、不允许“稍后重试”),它是逐请求的性质;99.99% 是时间占比或成功率,允许 0.01% 的请求失败或超时。二者不是一回事:一个 99.99% 可用的系统,在分区时只要有请求超时,就不满足 CAP 的 A。这也正是 Kleppmann 批评 CAP 的原因之一——按这个定义,几乎所有真实系统都不“可用”,定理于是很少真正适用。
Q2 单机 MySQL 能不能说自己是“CA 系统”?在什么意义上成立,在什么意义上不成立?
▸ 答案:在“没有跨节点复制”这个意义上成立——CAP 的 P 描述的是节点之间的消息丢失,单机没有第二个节点,自然不存在分区,一致性与可用性可以同时满足。不成立的地方在工程层面:客户端到这台机器的网络也会断,此时对客户端而言服务不可用;机器本身会坏,没有副本就没有容灾。所以“CA”只是“还没进入分布式”的同义词,不是一种可以拿来宣传的高可用属性。
Q3 在 17.1 的证明里,如果分区中的 B 返回“系统繁忙,请稍后重试”,算不算满足 CAP 的 A?为什么?
▸ 答案:不算。CAP 的 A 要求请求得到一个响应,这里的“响应”指这次操作完成、返回结果;返回错误、让客户端重试、或者一直挂着,都等于这次操作没有完成。这也解释了为什么“限流返回 429”“熔断返回 503”在工程上是正确的降级,但在 CAP 的账本上仍然记作“不可用”——两套口径不要混着用来论证结论。
Q4 为什么说“用 CAP 选数据库”通常会选错?举两个被 CAP 标签掩盖的关键差异。
▸ 答案:因为 CAP 只描述分区时刻的 C/A 二选一,不描述日常状态。两个都被标成“AP”的系统,可能在延迟(跨地域多数派 vs 本地写)、冲突解决(LWW 会静默丢写,向量时钟只检测不解决)、事务能力(是否支持跨行原子写)、运维成本上完全不同。被掩盖的关键差异至少有两类:① 平时的延迟与一致性(PACELC 的 else 分支,17.4 PACELC);② 分区期间的降级形态(少数派是拒写、只读还是排队),这决定了故障时的数据风险与恢复成本(见 17.3 分区期间的取舍)。