Java 面试笔记VII · 01 / 05

Lesson 66 · 分布式系统设计

CAP 理论与一致性模型:从理论到工程取舍

高级·🔥 极高·#分布式·#CAP·#理论

第 1 站

面试官:CAP 理论是什么?工程中怎么取舍?

"你说 ZooKeeper 是 CP 系统,Eureka 是 AP 系统——它们到底差在哪?CAP 理论在真实工程中是怎么落地的?" —— 面试官想考察的不是你背不背得出三个字母,而是你面对分布式场景时的架构判断力。

2000 年,Eric Brewer 在 PODC 大会上提出 CAP 猜想;2002 年,Gilbert 和 Lynch 在论文中给出了严格证明。CAP 定理说的是:

CAP 定理
在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性)—— 三者最多只能同时满足两个。

三个特性的准确定义:

特性定义大白话
C — 一致性所有节点在同一时刻看到相同的数据你改了数据,别人立刻能看到
A — 可用性每个请求都能在合理时间内收到非错误响应系统随时能响应你的请求
P — 分区容错网络分区(节点间通信中断)时系统仍能继续运行网线被拔了系统也不会整体挂掉

关键认知:P 不是选择题,是必答题。在分布式环境下,网络分区是客观存在的、不可避免的。所以真正的选择只剩:

核心结论
当网络分区发生时:选 C 则牺牲 A(拒绝服务直到数据一致);选 A 则牺牲 C(允许返回旧数据/不一致数据)。
CAP 定理:三选二的取舍 C 一致性 A 可用性 P 分区容错 CP 系统 ZooKeeper, etcd AP 系统 Eureka, Cassandra CA(单节点) 非分布式场景 分布式系统必须容忍 P,所以实际只在 CP 和 AP 之间选择
图 1CAP 三角:分布式系统只能在 CP 和 AP 之间做选择
第 2 站

分区容错:为什么 P 是必选项?

很多人初学 CAP 时会想:能不能选 CA,干脆不让网络分区发生?答案是:在分布式系统中做不到

只要你的数据分布在多台机器上,就必然面临网络分区风险:

  • 交换机故障、光纤被挖断 —— 物理层不可靠
  • 跨机房、跨地域部署 —— 网络延迟和丢包是常态
  • 云环境中的虚拟机漂移、容器调度 —— 网络拓扑动态变化

当分区发生时,系统面临的选择:

选择行为代价典型系统
选 CP少数派分区拒绝写入,保证数据一致部分节点不可用(牺牲 A)ZooKeeper、etcd、HBase
选 AP所有分区都接受读写,容忍不一致可能读到旧数据(牺牲 C)Cassandra、DynamoDB、Eureka
"那 CAP 是不是太绝对了?实际上 C 和 A 不是非黑即白的。" —— 对,这就是为什么 CAP 之后又出现了 BASE 理论和多种一致性模型。

CAP 定理中的 C 指的是强一致性(线性一致性),而工程中大量使用的是最终一致性。同理,A 也不是"100% 可用",而是"在合理时间内响应"。所以 CAP 的取舍更像是一个光谱,而不是一个开关。

第 3 站

CP 系统:ZooKeeper / ZAB 与 etcd / Raft

CP 系统的核心原则:当网络分区发生时,少数派节点拒绝服务,只保留多数派节点的可用性,以此保证数据一致性。

ZooKeeper — ZAB 协议

ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)协议来保证一致性:

ZAB 核心机制 · ZooKeeper
// ZAB 两种模式
1. 崩溃恢复模式(Leader Election)
   - Leader 挂掉后,Follower 选举新 Leader
   - 必须选到包含最新数据的节点(zxid 最大的)
   - 新 Leader 同步数据给 Follower,确保多数派数据一致

2. 消息广播模式(Atomic Broadcast)
   - 所有写请求转发到 Leader
   - Leader 生成事务提案 → 发给 Follower
   - Follower 写入本地日志 → 返回 ACK
   - 收到多数派 ACK → Leader 发送 COMMIT
   - // 本质是一个简化版的 2PC

// 写一致性保证
顺序一致性:所有事务按 zxid 全局有序
原子性:事务要么全集群成功,要么全失败

etcd — Raft 协议

Raft 是另一种共识算法,相比 Paxos 更易理解:

Raft 核心流程 · etcd
// 三种角色
Leader  — 处理所有读写(可配置读走 Follower)
Follower — 被动接收日志,超时未收到心跳则发起选举
Candidate — 发起选举,获得多数票后成为 Leader

// 日志复制流程
1. Client 向 Leader 发送写请求
2. Leader 将日志条目追加到本地日志
3. Leader 并行发送 AppendEntries RPC 给所有 Follower
4. 收到多数派 ACK → Leader 将日志标记为 committed
5. Leader 将 committed 日志应用到状态机,返回结果给 Client

// 关键约束
Election Safety:一个任期最多一个 Leader
Log Matching:两个日志在相同索引和任期号处,内容相同
CP 系统的多数派决策(5 节点集群) Leader F1 F2 F3 ✕ F4 ✕ 网络分区 多数派(3/5)→ 正常服务 可以选举 Leader、提交日志 少数派(2/5)→ 拒绝服务 无法选举、写入失败
图 2CP 系统:网络分区时少数派拒绝服务,多数派保证数据一致

ZAB vs Raft 对比:

维度ZAB(ZooKeeper)Raft(etcd)
日志方向主节点发给从节点(推模型)Leader 发给 Follower(推模型)
Leader 选举Fast Paxos 变体,投票 + zxid 比较随机超时 + 投票 + 任期号比较
成员变更动态添加/移除节点联合共识(Joint Consensus)
读操作默认读 Leader(可配置读 Follower + sync)默认读 Leader(可配置 ReadIndex)
典型应用Hadoop、Kafka、HBase 配置中心Kubernetes、TiKV、Consul

Raft 还定义了三个子问题来简化理解:

  • Leader 选举:Follower 超时后变为 Candidate,向所有节点发 RequestVote,获得多数票后成为 Leader
  • 日志复制:Leader 将客户端请求追加到日志,通过 AppendEntries RPC 复制到 Follower
  • 安全性:已提交的日志条目不会丢失,且所有节点的已提交日志一致
"CP 系统能保证多强的可用性?" —— 在正常情况下(无分区),CP 系统的可用性很高。只有在分区发生时才会牺牲可用性。5 节点集群容忍 2 个节点故障,7 节点容忍 3 个——但节点越多,写入延迟越高(需要更多 ACK)。
面试破解
ZooKeeper 为什么不适合做注册中心?因为它在 Leader 选举期间(通常几十秒)整个集群不可用,这对于需要高可用服务发现的场景是不可接受的。这就是 Eureka 选择 AP 的原因。而 Nacos 通过双模式设计(临时实例 AP + 持久实例 CP)兼顾了两者。
第 4 站

AP 系统:Eureka、Cassandra 与 DynamoDB

AP 系统的核心原则:当网络分区发生时,所有分区都继续接受读写请求,代价是可能读到不一致的数据。

Eureka — 服务注册与发现

Eureka 的 AP 设计 · Spring Cloud
// Eureka 集群中每个节点都是对等的
// 节点间通过 P2P 复制,没有 Leader

服务注册:
  → 注册到任意一个 Eureka 节点
  → 该节点异步复制到所有其他节点
  → 复制失败?不管,继续接受新请求

服务发现:
  → 从任意节点查询
  → 可能返回的数据比其他节点"旧一点"
  → 但对于服务发现来说,几秒的延迟完全可以接受

// 关键设计:自我保护模式
Self-Preservation:
  当 15 分钟内超过 85% 的心跳没有收到时
  → 进入自我保护模式
  → 不剔除任何服务实例(宁可保留过期数据)
  → 宁可返回可能不正确的数据,也不让服务列表为空

Cassandra / DynamoDB — 最终一致性存储

这些系统借鉴了 Amazon Dynamo 论文的设计思想:

  • 向量时钟(Vector Clock):用于检测并发写冲突
  • Gossip 协议:节点间异步传播数据变更
  • 反熵(Anti-entropy):通过 Merkle Tree 定期同步差异数据
  • 可调一致性级别:客户端可以选择 ONE / QUORUM / ALL
Cassandra 的一致性级别
// 写操作的一致性级别
ONE     — 写到一个副本就返回(延迟最低,一致性最弱)
QUORUM  — 写到多数派副本才返回(N/2+1 个节点)
ALL     — 写到所有副本才返回(一致性最强,延迟最高)

// 读操作的一致性级别
ONE     — 从一个副本读取(最快,可能读到旧数据)
QUORUM  — 从多数派读取并比较(强一致性保证)
LOCAL_QUORUM — 只在本地数据中心的多数派中读取

// 强一致性的条件:R + W > N
// R = 读节点数,W = 写节点数,N = 副本总数
// 例:N=3, R=2, W=2 → 读写必有交集 → 读到最新数据
工程洞察
AP 不等于"不靠谱"。Eureka 的"宁可返回旧数据也不让服务列表为空"恰恰是服务发现场景的最佳选择——客户端有本地缓存,短暂的不一致不影响路由。
第 5 站

一致性模型:强一致、最终一致与因果一致

CAP 定理中的 C 是"强一致性",但工程中的一致性远不止这一种。理解一致性模型的光谱,才能做出正确的技术选型。

一致性模型定义保证典型场景
线性一致性
(Linearizability)
所有操作看起来像是在某个全局顺序下原子执行写入后立刻读到新值分布式锁、etcd
顺序一致性
(Sequential)
所有操作按某种顺序执行,每个线程的操作保持程序顺序全局有序,但不要求实时ZooKeeper(ZAB)
因果一致性
(Causal)
有因果关系的操作保证顺序,无因果关系的操作可能乱序"我发帖 → 你回复"这个因果不丢社交系统、聊天
最终一致性
(Eventual)
如果没有新写入,最终所有副本数据一致最终一致,但"最终"可能是几秒到几分钟DNS、Redis 主从
一致性模型:从强到弱的光谱 强一致 弱一致 线性一致 etcd 顺序一致 ZooKeeper 因果一致 社交系统 最终一致 DNS, Redis 弱一致 UDP 广播 ← 一致性越强,延迟越高、可用性越低 —— ← 一致性越弱,延迟越低、可用性越高 ——
图 3一致性模型从强到弱的光谱,工程中根据业务需求选择合适的模型

因果一致性是一个常被忽视但非常实用的模型。它只保证有因果关系的操作顺序,比如"发帖→回帖",而对于没有因果关系的操作(比如两个人同时在不同地方发帖),则不保证顺序。这在社交媒体、即时通讯等场景中非常自然。

第 6 站

BASE 理论:CAP 的工程化延伸

BASE 理论是 eBay 架构师 Dan Pritchett 提出的,是对 CAP 中 AP 方向的工程化实践指导:

BASE = BA + S + E
BAsically Available(基本可用)
Soft State(软状态)
Eventually Consistent(最终一致性)
原则含义工程实践
基本可用允许损失部分可用性(响应时间增长、部分功能降级)限流降级、故障隔离、灰度发布
软状态允许系统中的数据存在中间状态,且该状态不影响系统整体功能缓存与 DB 的短暂不一致、MQ 消息的中间状态
最终一致数据最终达到一致,但允许短暂的不一致窗口异步复制、重试机制、对账补偿
BASE 在电商系统中的应用
// 场景:用户下单扣减库存

基本可用:
  大促时库存扣减压力大 → 允许先扣减 Redis 缓存库存
  异步同步到数据库 → 用户感知到的是"下单成功"

软状态:
  购物车数量、商品浏览量 → 允许短暂不准确
  推荐列表 → 允许几秒前的缓存数据

最终一致:
  订单创建 → 发送 MQ 消息 → 库存服务异步扣减
  如果扣减失败 → 定时对账任务补偿
  // 保证"订单状态"和"库存数量"最终一致

ACID vs BASE 对比:

维度ACID(传统关系型数据库)BASE(分布式系统)
一致性强一致(每次读都能读到最新写入)最终一致(允许短暂不一致窗口)
隔离性事务隔离(Serializable / RR / RC)弱隔离(允许并发修改可见)
可用性低(锁竞争、事务阻塞)高(基本可用,降级容忍)
适用场景单机/单库事务、金融核心系统大规模分布式系统、互联网应用
典型技术MySQL、Oracle、PostgreSQLCassandra、DynamoDB、Redis
BASE 在不同业务层的应用
// 核心交易层 — 偏 CP
支付、转账、库存扣减:
  必须保证数据一致性 → 宁可暂时不可用
  使用 TCC 或 Saga 分布式事务保证最终一致
  // BASE 的应用:允许异步扣减,但必须有对账补偿

// 中间业务层 — BASE 的典型应用
订单状态同步、物流追踪、评价系统:
  订单创建成功 → 异步通知物流系统
  物流状态变化 → 异步更新订单页面
  // 用户看到的状态可能有几秒延迟,完全可接受

// 边缘业务层 — 纯 AP
推荐列表、搜索排序、广告曝光:
  推荐算法算出的结果缓存 5 分钟
  新上架商品可能在 5 分钟后出现在推荐中
  // 不影响用户体验,反而降低系统压力

最终一致性的延迟窗口:

  • 毫秒级:Redis 主从复制(通常 < 1ms)
  • 秒级:MySQL 主从复制(通常 1-5 秒)、消息队列消费延迟
  • 分钟级:跨地域数据同步、定时对账任务
  • 小时级:数据仓库 ETL、离线分析任务
"BASE 是不是意味着放弃一致性?" —— 不是放弃,而是把一致性从"每时每刻"放宽到"最终"。关键是要有补偿机制:对账、重试、告警。BASE 不是偷懒的借口,而是精心设计的取舍。选择 BASE 的前提是你已经评估过不一致的代价可承受。
第 7 站

实战:工程中如何做出 CP/AP 选择?

面对一个具体的业务场景,选择 CP 还是 AP?问自己三个问题:

决策维度选 CP选 AP
数据不一致的后果资金、订单等核心数据,不一致 = 资损用户头像、昵称,短暂不一致可接受
对可用性的容忍度能接受短暂的不可用(如配置中心重启)必须 7×24 可用(如注册中心、DNS)
延迟要求能接受几十毫秒到秒级的同步延迟需要毫秒级响应

常见组件的 CAP 定位:

常见分布式组件选型参考
// CP 阵营 —— 数据一致性优先
ZooKeeper  — 配置中心、分布式锁、Leader 选举(ZAB)
etcd       — K8s 元数据存储、服务发现(Raft)
HBase      — 大数据强一致存储(依赖 ZK)
TiKV       — 分布式 KV,金融级强一致(Raft)

// AP 阵营 —— 可用性优先
Eureka     — Spring Cloud 服务注册发现(P2P 复制)
Cassandra  — 大规模写入(Gossip + 可调一致性)
DynamoDB   — AWS 托管 NoSQL(向量时钟 + 最终一致)
Redis Cluster — 主从异步复制,故障时可能丢少量写入

// 混合/可配置
Consul     — 默认 CP(Raft),但也支持 AP 模式
Nacos      — 临时实例用 AP(Distro),持久实例用 CP(Raft)
MongoDB    — 默认最终一致,可配置 w=majority 达到强一致
Nacos 的双模式设计:根据实例类型选择一致性协议 临时实例 — AP 模式 Distro 协议(P2P 异步复制) 客户端心跳维持实例存活 节点间异步同步注册信息 允许短暂不一致,保证高可用 适合:微服务实例注册 持久实例 — CP 模式 JRaft 协议(类 Raft) 服务端主动探测实例健康 多数派写入保证一致性 Leader 选举期间短暂不可用 适合:DNS、配置管理
图 4Nacos 同时支持 AP 和 CP 模式,是工程中灵活选择的典范

面试常考场景分析:

场景决策 · 面试中如何回答 CP vs AP 选择
// 场景一:秒杀库存扣减
分析:库存数据不一致 → 超卖 → 资损
结论:选 CP 方向,用 Redis 原子扣减 + 数据库最终一致

// 场景二:用户注册
分析:注册成功后立即登录,如果注册数据和登录数据不一致 → 用户体验差
结论:选 CP 方向,注册写入主库后同步完成再返回

// 场景三:朋友圈/动态
分析:发布动态后朋友看到的时间有先后 → 完全可接受
结论:选 AP 方向,异步推送到粉丝的 Feed 列表

// 场景四:分布式配置中心
分析:配置变更必须所有节点一致生效 → 否则行为不可预测
结论:选 CP 方向,用 ZooKeeper/Nacos 持久实例

// 场景五:商品详情页浏览
分析:价格、描述等数据变更,用户看到旧版 → 影响不大
结论:选 AP 方向,缓存 + 异步更新
面试回答模板
"选择 CP 还是 AP 取决于业务对数据不一致的容忍度。核心交易数据(资金、订单)选 CP,非核心数据(推荐、日志)选 AP。工程中很多系统是混合的:比如 Nacos 临时实例用 AP 保证高可用,持久实例用 CP 保证一致性。"
本课文脑图回顾
  • CAP 三选二:分布式系统中 P 是必选项,实际在 CP 和 AP 之间选择
  • CP 系统(ZooKeeper/etcd):多数派决策,少数派不可用,保证强一致性
  • AP 系统(Eureka/Cassandra):所有分区可服务,容忍短暂不一致
  • 一致性模型是一个光谱:线性一致 → 顺序一致 → 因果一致 → 最终一致
  • BASE 理论是 CAP 的工程化延伸:基本可用 + 软状态 + 最终一致
  • 工程选型核心三问:数据不一致后果?可用性容忍度?延迟要求?