CAP 不是在三个特性里挑两个,而是断言:网络分区一旦发生,系统必须在一致性和可用性之间二选一。本文从 Gilbert/Lynch 的证明定义出发讲清 C、A、P 的严格含义,梳理 BASE 理论作为 AP 方向工程妥协的定位,并用 ZooKeeper 与 Eureka 的对照展示取舍如何落到真实系统。

它解决什么问题

分布式系统靠多副本冗余数据,而副本之间必须通过网络同步,网络恰恰不可靠:机房光缆被挖断、交换机故障、丢包与延迟抖动,都会让节点之间失联,形成网络分区。分区一旦发生,被隔开的两半边可能同时收到写请求。此时系统只有两条路:要么拒绝其中一侧的读写,保住”任意时刻读到的都是最新值”的一致性;要么两边照常服务,接受两侧数据暂时分叉。CAP 定理论证了这个二选一无法回避。BASE 则回答选择了可用性之后的问题:分叉的数据最终如何收敛。

核心原理

CAP 三要素的严格含义

2000 年 Eric Brewer 在 PODC 研讨会上提出猜想,2002 年 MIT 的 Seth Gilbert 和 Nancy Lynch 给出形式化证明,CAP 从此从经验之谈升级为定理。但证明语境中的定义与日常口语有明显出入:

  • 一致性(Consistency):指线性一致性(linearizability,证明中称 atomic consistency)。写操作成功返回后,之后发起的任何读都必须得到该值或更新的值,仿佛数据只有一份。注意它与 ACID 里的 Consistency(事务前后满足完整性约束)完全是两回事,只是恰好同名。
  • 可用性(Availability):任何一个非故障节点收到请求,都必须给出非错误的响应。它不含延迟要求,也不要求响应里是新数据——“一秒内返回”属于 SLA,不是 CAP 的 A。
  • 分区容错(Partition Tolerance):网络允许丢失节点之间任意多的消息。P 不是一个可以勾选的功能,而是异步网络的客观现实。

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

流传最广的误读是”C、A、P 任意挑两个”。按这种读法,CA 架构似乎成立:放弃 P,只保一致与可用。但放弃 P 意味着假设网络永不分区,而只要存在跨节点通信,分区就是迟早会发生的事件——能同时满足 C 和 A 的只有单机系统。Brewer 本人在 2012 年的回顾文章中专门澄清:分区是少数时刻出现的异常状态,正确的表述是”分区发生时在 C 与 A 之间取舍;无分区时 C 和 A 可以兼得”。所以讨论 CAP 的正确姿势不是给系统贴”三选二”标签,而是回答”分区到来的那一刻,我选择牺牲什么”。

BASE:把妥协写成方法论

BASE 由 eBay 架构师 Dan Pritchett 于 2008 年提出,是 Basically Available、Soft State、Eventually Consistent 的缩写,论文标题就叫《BASE: An ACID Alternative》:

  • 基本可用:故障时允许有损——响应变慢、非核心功能降级,但主干服务不中断。
  • 软状态:允许数据存在中间态,例如”库存已扣减、订单未创建”,中间态不阻塞系统对外服务。
  • 最终一致:若不再产生新写入,所有副本经过有限时间收敛到相同值。

定位上,BASE 首先是 ACID 在分布式场景的替代品;同时它与 CAP 的 AP 路线天然衔接——选择 AP 意味着放弃强一致,而 BASE 正是”放弃强一致之后如何让系统收敛”的工程指南。可以说 CAP 划出物理边界,BASE 给出边界内的活法。

深入机制:分区时刻的推演

用一个两副本的极简模型推演取舍。客户端向 N1 写入 x=1,N1 需同步给 N2 才算一致。此时网络分区,N1 与 N2 失联:

正常期:      写 x=1 -> N1 -> 同步 -> N2 -> 返回成功   (C 与 A 同时满足)
分区后(选C): 写 x=1 -> N1 无法同步 N2 -> 阻塞或拒绝请求 (牺牲 A)
分区后(选A): 写 x=1 -> N1 直接返回成功; 另一侧 N2 收到 x=2
-> 两侧数据分叉,恢复后需要合并 (牺牲 C)

三条路径里第三条被物理定律划掉,这就是定理的全部内容。但 CAP 没有覆盖无分区时的常态权衡,Daniel Abadi 提出 PACELC 补全:If Partition,在 Availability 与 Consistency 之间选;Else,在 Latency 与 Consistency 之间选。MySQL 主从的异步复制延迟低但可能读到旧值(EL),半同步复制更一致但更慢(EC),正是 E 分支的日常体现。

工程实践:ZooKeeper 与 Eureka

注册中心是最直观的 CP/AP 对照样本。

ZooKeeper 偏 CP:写请求走 Leader 经 ZAB 协议过半提交,保证线性化写入;当集群处于选举中或在线节点不足法定人数时,直接拒绝服务,宁可不可用也不返回相互矛盾的视图。默认读走本地节点,可能短暂滞后但严格按 zxid 顺序推进,需要最新视图可显式 sync。

Eureka 偏 AP:Server 之间对等复制,没有 Leader,分区两侧都能继续接收注册与查询,代价是两侧注册表可能不一致一段时间。其自我保护机制把”保可用”做到极致:当单位时间实际续约数低于期望值的 85% 时停止剔除实例,宁可保留可能已下线的僵尸条目,也不在误判场景下大规模清空服务列表。

选型结论取决于业务容忍度:配置下发、分布式锁这类”错一次就出事故”的场景选 CP;服务发现这类”列表旧一点、靠客户端重试兜底”的场景选 AP。Nacos 同时提供两种模式,本质也是把这个开关交还给用户。

对比与权衡

维度 CP 路线 AP 路线
分区时行为 少数派拒绝服务,等待恢复 两侧继续服务,数据分叉
一致性保证 线性一致(或顺序一致) 最终一致
代表系统 ZooKeeper、etcd、HBase Eureka、Cassandra、DynamoDB
典型场景 分布式锁、配置中心、选主 服务发现、购物车、社交 feed
恢复后工作 直接对外服务 需要冲突合并与对账

面试视角

CAP 是”三选二”吗? 不是。P 描述的是异步网络必然存在的消息丢失,无法放弃。定理真正的断言是:分区发生时,线性一致与”每个非故障节点都必须应答”不可兼得;CA 只对单机系统成立。Brewer 2012 年的回顾文章对此有专门澄清。

CAP 的 C 和 ACID 的 C 一样吗? 不一样。前者是线性一致性,约束的是”多个副本看起来像一份数据”;后者是完整性约束,约束的是”事务前后数据满足业务规则”,两者只是恰好同名。

BASE 与 CAP 是什么关系? BASE 原名就是”ACID 的替代品”,解决分布式事务的放松问题;同时它的最终一致性恰好是 AP 系统的收敛手段,所以实践中常被一起讨论,但严格说它不是 CAP 的补充理论,而是 ACID 的替代方案兼 AP 路线的工程指南。

ZooKeeper 和 Eureka 分区时表现有何不同? ZK 在丢失多数派或选举期间拒绝服务,保住一致视图;Eureka 节点对等、继续服务,靠自我保护机制避免误剔除,接受注册表短暂分叉,恢复后靠复制收敛。

最终一致性怎么落地? 常见手段有读修复、提示移交(hinted handoff)、反熵同步(常用 Merkle 树定位差异),业务侧则配合版本号、幂等写入与定时对账任务。

小结

  • CAP 的精确语义是”分区时 C 与 A 二选一”,不是三选二;CA 只存在于单机系统。
  • PACELC 把视野补全到无分区时期:延迟与一致性同样是持续存在的权衡。
  • BASE 是 ACID 的分布式替代品,也是 AP 路线的工程收敛指南。
  • 给系统贴 CP/AP 标签不如逐条操作路径分析:同一个系统,写路径可能偏 CP、读路径可能偏 AP。评审架构时要说清”分区时用户看到什么、恢复后如何对账”。