CAP 定理

2025 年双十一零点,某电商平台的库存服务出现了超卖问题。

库存服务是一个典型的分布式系统,多个节点各自管理一部分库存。用户下单时,需要在多个节点间协调扣减库存。零点高峰期,由于网络抖动,三个机房之间的网络出现了短暂的分区。问题随之暴露:

  • 节点 A 认为库存充足,扣减成功
  • 节点 B 认为库存充足,扣减成功
  • 节点 C 认为库存充足,扣减成功

但实际库存只有 1 件,三次扣减都成功了。这就是 CAP 定理在生产环境中的真实体现。

【架构权衡】 CAP 定理不是理论游戏,而是分布式系统设计的铁律。在设计分布式系统时,你必须在 C、A、P 三者之间做出取舍。而现实中,由于网络分区不可避免(P 必须满足),我们实际上只能选择 CP 或 AP。


一、问题背景

1.1 真实事故场景

某金融支付系统,采用了多机房部署。架构设计时,团队选择了"强一致性"路线,所有交易必须实时同步到所有机房。

双十一期间,其中一个机房的网络设备故障,导致与其他机房的网络中断 30 秒。

正常状态:
[机房A] ←同步→ [机房B] ←同步→ [机房C]

[用户下单] → 实时同步到所有机房 → 强一致

网络分区状态:
[机房A] ← ✗断开 ✗ → [机房B] ← ✗断开 ✗ → [机房C]

选择 CP 的系统:在分区期间,机房 B 和 C 无法同步,停止服务(拒绝写入),保证了数据一致性,但损失了可用性。

选择 AP 的系统:在分区期间,每个机房独立服务,接受了数据不一致的风险,但保证了可用性。

1.2 CAP 的定义

CAP 定理(Brewer's Theorem):

在一个分布式系统中,以下三个特性不可能同时满足:

┌─────────────────────────────────────────────────────────────┐
│                         C (Consistency)                      │
│            一致性:所有节点看到相同的数据                    │
├─────────────────────────────────────────────────────────────┤
│                         A (Availability)                      │
│             可用性:每个请求都能收到响应                    │
├─────────────────────────────────────────────────────────────┤
│                         P (Partition Tolerance)             │
│              分区容错:系统能容忍网络分区                   │
└─────────────────────────────────────────────────────────────┘

【架构权衡】 这里有一个关键误解:很多人以为 CAP 意味着"三选二"。但实际上,分区容错(P)是分布式系统必须具备的能力——因为网络故障不可避免。所以真实的抉择是:在保证 P 的前提下,选择 C 还是 A

二、方案演进

2.1 CP 系统:牺牲可用性换取一致性

代表系统:ZooKeeper、HBase、MongoDB(副本集模式)、etcd

CP 系统特点:

分区发生前:
[A节点] ←实时同步→ [B节点] ←实时同步→ [C节点]

              数据完全一致

分区发生后(AB 连通,BC 断开):
[A节点] ←实时同步→ [B节点]
                      ↓ ✗ 断开 ✗
                    [C节点]

            B 无法同步到 C
            B 停止服务(拒绝写入)
            C 也停止服务(拒绝写入)
            系统整体不可用,但数据一致

典型案例:ZooKeeper 在网络分区时的行为

ZooKeeper 使用 ZAB 协议保证强一致性。当发生网络分区时:

  • 如果 Leader 所在分区节点数不足半数,整个集群停止服务
  • 客户端无法写入,直到网络恢复
// ZooKeeper 写入流程
// 写入必须经过 Leader,且需要多数节点确认
WriteRequest → Leader → [Follower1, Follower2] → 等待多数ACK → 提交

2.2 AP 系统:牺牲一致性换取可用性

代表系统:Cassandra、DynamoDB、CouchDB、Redis Cluster(哨兵模式部分场景)

AP 系统特点:

分区发生前:
[A节点] ←实时同步→ [B节点] ←实时同步→ [C节点]

              数据完全一致

分区发生后(AB 连通,BC 断开):
[A节点] ←实时同步→ [B节点]
                      ↓ ✗ 断开 ✗
                    [C节点]

            C 独立运行,接受写入
            可能产生数据冲突
            但系统始终可用

典型案例:Amazon DynamoDB 的最终一致性

DynamoDB 允许多个副本异步同步,在网络分区时:

  • 客户端可以在任何副本写入
  • 副本间通过向量时钟或时间戳解决冲突
  • 最终达到一致(可能需要几秒到几分钟)

2.3 方案对比

维度CP 系统AP 系统
一致性强度强一致性最终一致性
可用性分区期间可能不可用分区期间始终可用
数据完整性保证分区期间可能丢失或冲突
适用场景金融、订单、库存等强一致性需求社交Feed、日志、实时数据展示
典型产品ZooKeeper、etcd、HBaseCassandra、DynamoDB、Redis Cluster
延迟较高(同步确认开销)较低(异步写入)

【架构权衡】 选择 CP 还是 AP,本质上是"业务场景"的决策:

  • 金融支付:宁可不可用,也要保证数据正确 —— CP
  • 社交 Feed:宁可展示稍旧的数据,也要保证页面秒开 —— AP
  • 库存超卖:宁可拒绝订单,也不能超卖 —— CP

三、核心设计

3.1 为什么 P 必须满足?

分布式系统的三个假设:

1. 网络不是可靠的(The network is not reliable)
2. 延迟不是零(Latency is not zero)
3. 带宽不是无限的(Bandwidth is not infinite)

结论:网络分区(Partition)在分布式系统中必然发生
       所以 P 不是可选项,而是必选项

3.2 一致性的不同级别

CAP 中的 C(强一致性)不是唯一的选择:

一致性级别说明典型实现
线性一致性(Linearizability)所有操作看起来是原子的,按全局时钟排序Zookeeper, etcd
顺序一致性(Sequential)所有进程看到的操作顺序一致,但不要求按全局时钟Cassandra
因果一致性(Causal)满足因果关系的操作顺序一致某些消息队列
最终一致性(Eventual)不保证立即一致,但最终会达到一致DynamoDB, Cassandra
弱一致性(Weak)只保证读到上次写入的值DNS, CDN
一致性级别金字塔:

  强一致性(Linearizability)

  顺序一致性(Sequential)

  因果一致性(Causal)

  最终一致性(Eventual)

  弱一致性(Weak)

3.3 BASE 理论:CAP 的实际工程折中

BASE 理论 = Basically Available + Soft state + Eventually consistent

┌─────────────────────────────────────────────────────────────┐
│                    Basically Available                        │
│         基本可用:系统保证核心功能可用                        │
│         分区期间:可以降级、延迟响应、返回默认值             │
├─────────────────────────────────────────────────────────────┤
│                       Soft state                             │
│                   软状态:数据状态可以不同步                  │
│            分区期间:各节点数据副本可能暂时不一致              │
├─────────────────────────────────────────────────────────────┤
│                   Eventually consistent                      │
│              最终一致:系统在一定时间后达到一致                │
│            分区恢复后:通过冲突解决、数据同步达到一致          │
└─────────────────────────────────────────────────────────────┘

【架构权衡】 BASE 理论告诉我们:在工程实践中,不需要追求完美的 CAP 满足,而是通过系统设计,在可用性和一致性之间找到业务可接受的平衡点。

四、生产避坑

4.1 分区期间的决策陷阱

陷阱一:试图在代码层面"绕过"CAP 限制

有些团队试图通过"重试 + 幂等"在 AP 系统中实现 CP:

// ❌ 错误做法:在 AP 系统中强制实现 CP 效果
@Service
public class OrderService {
    public void createOrder(Order order) {
        // 多次重试试图保证一致性
        for (int i = 0; i < 3; i++) {
            try {
                // 写入分布式存储
                distributedStorage.write(order);
                // 检查是否写入成功
                if (checkConsistency(order)) {
                    break;
                }
            } catch (Exception e) {
                log.warn("写入失败,重试 {}/3", i + 1);
            }
        }
    }
}

问题:这种方式并不能真正保证 CP,只是降低了不一致的概率。正确的做法是在架构选型阶段就决定 CP 还是 AP。

陷阱二:混淆"可用性"和"可降级"

❌ 可用性 = 系统必须 100% 可用
✅ 可用性 = 系统能正常响应请求(即使返回降级数据)

AP 系统在分区期间:

  • ✅ 返回降级数据(可用)
  • ✅ 返回默认值(可用)
  • ❌ 拒绝所有请求(不可用,这是 CP 的选择)

4.2 CAP 决策树

你需要强一致性吗?
├─ 是(金融、库存、订单)
│     └─ 选择 CP 系统(ZooKeeper、etcd、HBase)

└─ 否(社交Feed、日志、展示数据)
      └─ 选择 AP 系统(Cassandra、DynamoDB)
      └─ 通过补偿机制处理数据冲突

五、工程代价评估

维度CP 系统AP 系统
运维成本高(需要强网络监控、快速故障检测)中(需要冲突解决逻辑)
排障复杂度高(分区期间服务不可用,需要快速恢复)高(数据冲突难以排查)
扩展性较低(受限于一致性协议)高(无中心节点,可水平扩展)
回滚风险高(数据一致性要求高,回滚慎重)中(数据最终一致,回滚相对容易)

六、落地 Checklist

  • 架构设计阶段:明确系统是一致性优先还是可用性优先
  • 选型阶段:根据业务场景选择 CP 或 AP 系统
  • 设计阶段:设计数据冲突解决策略(对于 AP 系统)
  • 降级设计:设计分区期间的降级策略和降级数据
  • 监控部署:部署分区检测机制(多数投票、心跳检测)
  • 故障演练:定期演练网络分区场景,验证系统行为
  • 数据修复:设计分区恢复后的数据修复流程