Lesson 66 · 分布式系统设计
CAP 理论与一致性模型:从理论到工程取舍
面试官:CAP 理论是什么?工程中怎么取舍?
2000 年,Eric Brewer 在 PODC 大会上提出 CAP 猜想;2002 年,Gilbert 和 Lynch 在论文中给出了严格证明。CAP 定理说的是:
在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性)—— 三者最多只能同时满足两个。
三个特性的准确定义:
| 特性 | 定义 | 大白话 |
|---|---|---|
| C — 一致性 | 所有节点在同一时刻看到相同的数据 | 你改了数据,别人立刻能看到 |
| A — 可用性 | 每个请求都能在合理时间内收到非错误响应 | 系统随时能响应你的请求 |
| P — 分区容错 | 网络分区(节点间通信中断)时系统仍能继续运行 | 网线被拔了系统也不会整体挂掉 |
关键认知:P 不是选择题,是必答题。在分布式环境下,网络分区是客观存在的、不可避免的。所以真正的选择只剩:
分区容错:为什么 P 是必选项?
很多人初学 CAP 时会想:能不能选 CA,干脆不让网络分区发生?答案是:在分布式系统中做不到。
只要你的数据分布在多台机器上,就必然面临网络分区风险:
- 交换机故障、光纤被挖断 —— 物理层不可靠
- 跨机房、跨地域部署 —— 网络延迟和丢包是常态
- 云环境中的虚拟机漂移、容器调度 —— 网络拓扑动态变化
当分区发生时,系统面临的选择:
| 选择 | 行为 | 代价 | 典型系统 |
|---|---|---|---|
| 选 CP | 少数派分区拒绝写入,保证数据一致 | 部分节点不可用(牺牲 A) | ZooKeeper、etcd、HBase |
| 选 AP | 所有分区都接受读写,容忍不一致 | 可能读到旧数据(牺牲 C) | Cassandra、DynamoDB、Eureka |
CAP 定理中的 C 指的是强一致性(线性一致性),而工程中大量使用的是最终一致性。同理,A 也不是"100% 可用",而是"在合理时间内响应"。所以 CAP 的取舍更像是一个光谱,而不是一个开关。
CP 系统:ZooKeeper / ZAB 与 etcd / Raft
CP 系统的核心原则:当网络分区发生时,少数派节点拒绝服务,只保留多数派节点的可用性,以此保证数据一致性。
ZooKeeper — ZAB 协议
ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)协议来保证一致性:
// 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 更易理解:
// 三种角色
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:两个日志在相同索引和任期号处,内容相同
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
- 安全性:已提交的日志条目不会丢失,且所有节点的已提交日志一致
AP 系统:Eureka、Cassandra 与 DynamoDB
AP 系统的核心原则:当网络分区发生时,所有分区都继续接受读写请求,代价是可能读到不一致的数据。
Eureka — 服务注册与发现
// Eureka 集群中每个节点都是对等的
// 节点间通过 P2P 复制,没有 Leader
服务注册:
→ 注册到任意一个 Eureka 节点
→ 该节点异步复制到所有其他节点
→ 复制失败?不管,继续接受新请求
服务发现:
→ 从任意节点查询
→ 可能返回的数据比其他节点"旧一点"
→ 但对于服务发现来说,几秒的延迟完全可以接受
// 关键设计:自我保护模式
Self-Preservation:
当 15 分钟内超过 85% 的心跳没有收到时
→ 进入自我保护模式
→ 不剔除任何服务实例(宁可保留过期数据)
→ 宁可返回可能不正确的数据,也不让服务列表为空
Cassandra / DynamoDB — 最终一致性存储
这些系统借鉴了 Amazon Dynamo 论文的设计思想:
- 向量时钟(Vector Clock):用于检测并发写冲突
- Gossip 协议:节点间异步传播数据变更
- 反熵(Anti-entropy):通过 Merkle Tree 定期同步差异数据
- 可调一致性级别:客户端可以选择 ONE / QUORUM / ALL
// 写操作的一致性级别
ONE — 写到一个副本就返回(延迟最低,一致性最弱)
QUORUM — 写到多数派副本才返回(N/2+1 个节点)
ALL — 写到所有副本才返回(一致性最强,延迟最高)
// 读操作的一致性级别
ONE — 从一个副本读取(最快,可能读到旧数据)
QUORUM — 从多数派读取并比较(强一致性保证)
LOCAL_QUORUM — 只在本地数据中心的多数派中读取
// 强一致性的条件:R + W > N
// R = 读节点数,W = 写节点数,N = 副本总数
// 例:N=3, R=2, W=2 → 读写必有交集 → 读到最新数据
一致性模型:强一致、最终一致与因果一致
CAP 定理中的 C 是"强一致性",但工程中的一致性远不止这一种。理解一致性模型的光谱,才能做出正确的技术选型。
| 一致性模型 | 定义 | 保证 | 典型场景 |
|---|---|---|---|
| 线性一致性 (Linearizability) | 所有操作看起来像是在某个全局顺序下原子执行 | 写入后立刻读到新值 | 分布式锁、etcd |
| 顺序一致性 (Sequential) | 所有操作按某种顺序执行,每个线程的操作保持程序顺序 | 全局有序,但不要求实时 | ZooKeeper(ZAB) |
| 因果一致性 (Causal) | 有因果关系的操作保证顺序,无因果关系的操作可能乱序 | "我发帖 → 你回复"这个因果不丢 | 社交系统、聊天 |
| 最终一致性 (Eventual) | 如果没有新写入,最终所有副本数据一致 | 最终一致,但"最终"可能是几秒到几分钟 | DNS、Redis 主从 |
因果一致性是一个常被忽视但非常实用的模型。它只保证有因果关系的操作顺序,比如"发帖→回帖",而对于没有因果关系的操作(比如两个人同时在不同地方发帖),则不保证顺序。这在社交媒体、即时通讯等场景中非常自然。
BASE 理论:CAP 的工程化延伸
BASE 理论是 eBay 架构师 Dan Pritchett 提出的,是对 CAP 中 AP 方向的工程化实践指导:
BAsically Available(基本可用)
Soft State(软状态)
Eventually Consistent(最终一致性)
| 原则 | 含义 | 工程实践 |
|---|---|---|
| 基本可用 | 允许损失部分可用性(响应时间增长、部分功能降级) | 限流降级、故障隔离、灰度发布 |
| 软状态 | 允许系统中的数据存在中间状态,且该状态不影响系统整体功能 | 缓存与 DB 的短暂不一致、MQ 消息的中间状态 |
| 最终一致 | 数据最终达到一致,但允许短暂的不一致窗口 | 异步复制、重试机制、对账补偿 |
// 场景:用户下单扣减库存
基本可用:
大促时库存扣减压力大 → 允许先扣减 Redis 缓存库存
异步同步到数据库 → 用户感知到的是"下单成功"
软状态:
购物车数量、商品浏览量 → 允许短暂不准确
推荐列表 → 允许几秒前的缓存数据
最终一致:
订单创建 → 发送 MQ 消息 → 库存服务异步扣减
如果扣减失败 → 定时对账任务补偿
// 保证"订单状态"和"库存数量"最终一致
ACID vs BASE 对比:
| 维度 | ACID(传统关系型数据库) | BASE(分布式系统) |
|---|---|---|
| 一致性 | 强一致(每次读都能读到最新写入) | 最终一致(允许短暂不一致窗口) |
| 隔离性 | 事务隔离(Serializable / RR / RC) | 弱隔离(允许并发修改可见) |
| 可用性 | 低(锁竞争、事务阻塞) | 高(基本可用,降级容忍) |
| 适用场景 | 单机/单库事务、金融核心系统 | 大规模分布式系统、互联网应用 |
| 典型技术 | MySQL、Oracle、PostgreSQL | Cassandra、DynamoDB、Redis |
// 核心交易层 — 偏 CP
支付、转账、库存扣减:
必须保证数据一致性 → 宁可暂时不可用
使用 TCC 或 Saga 分布式事务保证最终一致
// BASE 的应用:允许异步扣减,但必须有对账补偿
// 中间业务层 — BASE 的典型应用
订单状态同步、物流追踪、评价系统:
订单创建成功 → 异步通知物流系统
物流状态变化 → 异步更新订单页面
// 用户看到的状态可能有几秒延迟,完全可接受
// 边缘业务层 — 纯 AP
推荐列表、搜索排序、广告曝光:
推荐算法算出的结果缓存 5 分钟
新上架商品可能在 5 分钟后出现在推荐中
// 不影响用户体验,反而降低系统压力
最终一致性的延迟窗口:
- 毫秒级:Redis 主从复制(通常 < 1ms)
- 秒级:MySQL 主从复制(通常 1-5 秒)、消息队列消费延迟
- 分钟级:跨地域数据同步、定时对账任务
- 小时级:数据仓库 ETL、离线分析任务
实战:工程中如何做出 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 达到强一致
面试常考场景分析:
// 场景一:秒杀库存扣减
分析:库存数据不一致 → 超卖 → 资损
结论:选 CP 方向,用 Redis 原子扣减 + 数据库最终一致
// 场景二:用户注册
分析:注册成功后立即登录,如果注册数据和登录数据不一致 → 用户体验差
结论:选 CP 方向,注册写入主库后同步完成再返回
// 场景三:朋友圈/动态
分析:发布动态后朋友看到的时间有先后 → 完全可接受
结论:选 AP 方向,异步推送到粉丝的 Feed 列表
// 场景四:分布式配置中心
分析:配置变更必须所有节点一致生效 → 否则行为不可预测
结论:选 CP 方向,用 ZooKeeper/Nacos 持久实例
// 场景五:商品详情页浏览
分析:价格、描述等数据变更,用户看到旧版 → 影响不大
结论:选 AP 方向,缓存 + 异步更新
- CAP 三选二:分布式系统中 P 是必选项,实际在 CP 和 AP 之间选择
- CP 系统(ZooKeeper/etcd):多数派决策,少数派不可用,保证强一致性
- AP 系统(Eureka/Cassandra):所有分区可服务,容忍短暂不一致
- 一致性模型是一个光谱:线性一致 → 顺序一致 → 因果一致 → 最终一致
- BASE 理论是 CAP 的工程化延伸:基本可用 + 软状态 + 最终一致
- 工程选型核心三问:数据不一致后果?可用性容忍度?延迟要求?