Skip to content

[L2] CAP定理:一致性、可用性与分区容忍的三角权衡

一句话结论

网络分区不可避免,分布式系统只能在一致性与可用性之间二选一。


体系讲解

1. 三个属性的含义

属性英文含义
一致性Consistency所有节点在同一时刻看到相同数据(线性一致性)
可用性Availability每个请求都能收到非错误响应(不保证数据最新)
分区容忍Partition Tolerance节点间网络故障时系统仍能继续运行

2. 为什么只能选两个

CAP 定理(Brewer 2000,Gilbert & Lynch 2002 形式化证明)指出:三者不可同时满足

实际上,分区容忍(P)在互联网分布式系统中是必选项——网络延迟、丢包、节点隔离随时发生,放弃 P 意味着放弃分布式本身。因此真正的选择是:

网络分区发生时:
  → 保 C(拒绝写入/返回错误,等待一致性恢复)  ——  CP 系统
  → 保 A(继续响应,接受数据可能过时)          ——  AP 系统

3. CA、CP、AP 的现实映射

组合典型场景代表系统
CA(无分区假设)单机数据库 / 同机房强同步单节点 MySQL,PostgreSQL 单主
CP要求强一致,分区时宁可拒绝服务ZooKeeper、etcd、HBase
AP要求高可用,分区时允许数据短暂不一致Cassandra、Eureka、CouchDB

4. 选型决策树

业务能接受"读到旧数据"吗?
  → 否(金融账务、配置中心、分布式锁)→ 选 CP
  → 是(购物车、点赞数、服务注册)     → 选 AP

5. CAP 的局限

  • CAP 只描述分区时的极端场景,正常运行时 C 与 A 并不冲突
  • "一致性"在 CAP 中特指线性一致性(最强模型),工程中常用更弱的最终一致性满足需求
  • PACELC 模型在此基础上补充了延迟维度(见独立题目)

考察意图

考察候选人是否理解 CAP 不是"随意选两个"的排列题,而是在分区容忍必选的前提下,根据业务对数据新鲜度的容忍程度在 C 与 A 之间做出选择;同时考察能否识别 CAP 的适用边界(线性一致性 vs 最终一致性的概念区分)。


追问链

  1. "CA 系统"是否真实存在?

    严格意义上不存在:只要是分布式系统就必须处理网络分区,放弃 P 等价于放弃分布式。实践中"CA"通常指单节点或同机房同步复制的场景,一旦网络发生分区就必须牺牲 A 或 C。

  2. CAP 中的 C 与 ACID 中的 C 有何区别?

    ACID 的 C(一致性)指事务执行前后业务约束不被破坏,是应用层语义;CAP 的 C(一致性)特指线性一致性,即所有节点对同一数据的读写像在单机上执行一样有序,是系统层保证。两者完全不同,面试中混淆是典型失分点。

  3. AP 系统如何弥补"不一致"带来的数据问题?

    通过 BASE 策略:①版本向量 / 时间戳检测冲突;②Last-Write-Wins 或业务级合并(如购物车合并)解决冲突;③异步补偿(对账、补单)修复最终数据;④向用户展示"数据可能稍有延迟"的提示。


易错点

  1. 把 P 当可选项:认为可以"选 CA 放弃分区容忍"。现实中分布式系统必须处理网络分区,"放弃 P"实际意味着退化为单机,CAP 的真正选择只在 C 与 A 之间。

  2. 混淆 CAP-C 与 ACID-C:CAP 的一致性是线性一致性(系统层),ACID 的一致性是业务约束不变性(应用层),两者含义、层次均不同。

  3. 认为 CP 系统完全不可用:CP 系统在分区时会拒绝部分写请求以保证一致性,但正常运行时完全可用;不是"分区=宕机",而是"分区=降级为只读或报错"。


代码示例

php
// 模拟 CP 行为:写操作在无法确认多数节点时拒绝服务
class CpKvStore
{
    private array $nodes;
    private int $quorum;

    public function __construct(array $nodes)
    {
        $this->nodes  = $nodes;
        $this->quorum = (int) floor(count($nodes) / 2) + 1; // 多数派
    }

    /** @throws \RuntimeException 分区时拒绝写入以保证一致性(CP 策略) */
    public function set(string $key, string $value): void
    {
        $acks = 0;
        foreach ($this->nodes as $node) {
            if ($node->write($key, $value)) {
                $acks++;
            }
        }
        if ($acks < $this->quorum) {
            // 未达多数派确认 → 拒绝(牺牲 A,保住 C)
            throw new \RuntimeException("写入未达 quorum({$acks}/{$this->quorum}),操作拒绝");
        }
    }
}

基于 Apache License 2.0 开源