Skip to content

一致性取舍:CAP 与 BASE —— 分布式系统的第一道选择题 ​

属于 S8 分布式理论 · 第一篇 上一篇:分布式系统能力全景 下一篇:底层存储与数据同步

任何分布式系统的第一个设计决策都是:分区/故障发生时,保一致还是保可用? 本篇把 CAP 与 BASE 讲到能落地:精确定义、证明、误区、选型决策,以及最终一致性的工程实现模式。

一、CAP:三个性质,两个只能留一个 ​

1.1 精确定义 ​

性质定义注意
C(Consistency)线性一致性:系统对外表现为单机,读返回最近一次写是线性一致,不是最终一致
A(Availability)每个请求在有限时间内返回非错误响应不要求返回最新值,只要求"有响应且非错"
P(Partition tolerance)网络分区(消息丢失/节点失联)时系统继续按 C 或 A 提供服务分区是必然事件,P 不可选

1.2 证明思路(一页纸) ​

设分区把节点分为 G1、G2,两组无法通信:

  1. 客户端写 x=1 到 G1,成功。
  2. 客户端向 G2 读 x:
    • 保 C → G2 必须返回 1,但做不到 → 只能拒绝/挂起 → 牺牲 A;
    • 保 A → G2 必须响应 → 只能返回旧值 → 与 G1 不一致 → 牺牲 C。

结论:分区存在时 C、A 不可兼得。而分区无法避免,所以真实系统只有 CP 或 AP 两种路线。

1.3 四个高频误区(面试直接背) ​

  1. "三选二随便选"是错的 —— P 是必选,不存在"CA"选项(CA 只成立在"假设永不分区"的单机语义下)。
  2. C 不是最终一致性 —— 最终一致性属于 BASE,CAP 框架不讨论它。
  3. CAP 只约束分区时刻 —— 无分区时 CP 系统(如 etcd)读写照常;不能用 CAP 解释日常行为。
  4. 粒度是数据/模块 —— 一个系统可以混合:核心数据 CP、非核心数据 AP。

1.4 PACELC:补上"无分区时"的权衡 ​

CAP 只回答分区时怎么选,PACELC 补全无分区时的选择:

Partition(分区时):Availability vs Consistency Else(无分区时):Latency vs Consistency

典型:多数派强一致系统(etcd)无分区时每次写也要等多数派 fsync——用延迟换一致;异步主从(MySQL 异步复制)写只需主库确认——用一致换低延迟。

二、选型决策:CP 还是 AP ​

场景选型理由
分布式锁、选主、配置下发CP(etcd/ZooKeeper)锁和配置必须精确,宁可拒绝也不能给错
服务注册发现AP(Eureka)读到过期实例列表可接受;注册中心整体不可用才是灾难
强一致业务存储CP(TiDB、HBase、MongoDB 默认)金融/订单等场景数据不能丢不能错
缓存、计数、FeedAP(Redis 异步复制、Cassandra)容忍短暂不一致,换取低延迟高可用
消息队列依语义:Kafka 未确认 ack=0/1 偏 AP;事务性消息用 ISR 多数派偏 CP看业务是否允许消息丢失

注册中心选型的完整追问链:

  • 为什么 Eureka 选 AP?→ 服务发现读的是"哪个实例活着",读到已下线实例最多是请求失败重试,可容忍;而注册中心宕机导致所有服务找不到彼此 = 全系统瘫痪,不可容忍。→ 所以保可用,牺牲"读到的一定最新"。
  • 那 etcd 为什么被 K8s 用?→ etcd 存的是集群状态和配置,读错会导致调度错误,必须强一致;且 etcd 是少数派才不可用,多数派侧依旧服务。

三、BASE:AP 路线的工程化 ​

3.1 三要素 ​

要素含义落地表现
基本可用(Basically Available)故障时整体仍可用,允许降级响应变慢、非核心功能关闭(降级)、限流削峰
软状态(Soft state)允许中间不一致状态存在副本间数据短暂不同步是正常态
最终一致(Eventually consistent)无新写入后,副本最终收敛靠异步传播 + 补偿收敛

3.2 与 ACID 的对比(背表) ​

维度ACIDBASE
一致性强一致,提交即生效最终一致,靠收敛
可用性一致性优先,故障可拒绝可用性优先,短暂不一致可接受
模型悲观(先锁后做)乐观(先做后补)
典型MySQL InnoDB 事务缓存、MQ、异步复制

四、最终一致性的工程实现模式(考点:必须能说出机制) ​

最终一致不是"放任不管",面试要能给出收敛机制:

4.1 异步复制 + 消息重试 ​

写主库 → 同步发本地消息 → MQ投递 → 消费端同步到从库/下游
         └ 失败进死信队列 → 重试 → 达到上限告警人工介入

4.2 本地消息表 + 事务消息(解决"业务成功但消息没发出") ​

text
① 开启本地事务:写业务表 + 写消息表(同库同事务)→ 提交
② 后台任务扫描消息表 status=pending → 投递 MQ
③ 消费成功后更新消息表 status=done
④ 超过阈值未 done → 重投 / 告警

保证:业务与消息同生共死,消息至少投递一次;配合消费端幂等做到"不重不丢"。

4.3 对账与定时补偿 ​

定时任务对比两侧数据(如订单表 vs 支付流水),发现差异 → 补发/回滚/告警。对账是最终一致的最后兜底,任何异步链路都要有。

4.4 重试必须配幂等 ​

异步同步必然重试,重试必然重复执行 → 唯一键 / 状态机 / token 幂等是铁律(与微服务篇一致)。

4.5 分布式事务模式(最终一致的强一致补充) ​

模式机制一致性
TCCTry 预留 → Confirm 提交 / Cancel 取消最终一致(强于纯异步)
Saga正向事务链 + 反向补偿事务最终一致
两阶段提交(2PC)准备 + 提交,协调者阻塞强一致(有协调者单点、阻塞问题,少用)

五、设计决策框架(面试怎么答) ​

回答"你这个系统 CP 还是 AP"的完整结构:

  1. 按数据分类:列出系统中的数据,标注一致性要求(余额/库存/订单=强;计数/Feed/缓存=弱)。
  2. 按组件选型:强一致数据用 etcd/MySQL 主从同步/分布式事务;弱一致用异步复制 + MQ。
  3. 说明降级路径:AP 部分分区时怎么降级(读旧缓存、关闭非核心);CP 部分分区时怎么处理(多数派继续,少数派拒绝)。
  4. 给收敛机制:MQ 重试、幂等、对账。

面试追问 ​

  • 问:Redis Cluster 是 CP 还是 AP? 取决于复制模式:异步复制下主挂切换可能丢最近写入 = AP;同步复制(wait 命令/红锁类)可近似 CP。缓存场景默认按 AP 容忍丢失设计。
  • 问:Nacos 为什么有 CP 和 AP 两种模式? 服务发现场景用 AP(可用优先),配置中心用 CP(配置必须一致),一个组件两种模式对应两种数据的一致性要求不同。
  • 问:能不能既保强一致又保高可用? 单机强一致可以,分布式下必须在分区时二选一;可用"多数派 + 降低少数派影响"缓解(CP 下多数派侧依然可用),但不能同时满足 CAP 定义下的 C+A+P。

持续学习,持续构建。