CAP 理论与最终一致性
CAP定理、BASE理论、一致性模型与分布式系统权衡。
1. 从”电话会议”说起:CAP 定理是什么
1.1 一个三难困境
想象三个城市的同事(A、B、C)要开电话会议,但网络不稳定——A 和 B 之间的线路断了,C 和两边都通。
现在 A 说了一句话。问题是:B 能不能同步听到这句话?
情况一(保一致):会议必须所有人同步,线路断了就暂停会议,等线路恢复再继续。→ 保证了”信息一致”,但牺牲了”会议可用”。
情况二(保可用):会议继续开,A、C 正常讨论,B 暂时听不到(等线路恢复后补上)。→ 保证了”会议可用”,但牺牲了”信息一致”(B 暂时是”旧信息”)。
你只能选一个:要么暂停会议等同步(一致),要么继续开会接受不同步(可用)。不可能”既继续开会、又立刻同步”——因为线路断了,物理上做不到。
1.2 CAP 定理
CAP 定理(由 Eric Brewer 在 2000 年提出,2002 年被证明)说的就是这个道理,只是主角换成了分布式系统:
在分布式系统中,一致性(Consistency)、可用性(Availability)、分区容忍性(Partition tolerance)这三个属性,最多只能同时满足两个。
| 属性 | 含义 | 电话会议类比 |
|---|---|---|
| 一致性 C | 所有节点看到相同的数据 | 所有参会者听到相同内容 |
| 可用性 A | 每个请求都能得到响应 | 会议持续进行 |
| 分区容忍性 P | 网络故障时系统仍能运行 | 线路断了仍能开会 |
1.3 关键理解:P 不是”可选的”
初学者常把 CAP 理解成”三选二:CA、CP、AP 随便挑”。这是错误的。正确理解是:
网络分区(P)不是一种选择,而是一种必然——网络随时可能故障。既然 P 无法避免,真正的选择只有两个:分区发生时,保 C(一致)还是保 A(可用)?
就像电话会议:线路断不断不是你决定的,你能决定的只是”断了之后怎么办”。
用大白话说:网络分区时,系统只有两个选择:
- CP(保一致):宁可不服务,也不能给错误数据。→ 分区期间,部分请求失败/等待
- AP(保可用):宁可数据暂时不一致,也不能拒绝服务。→ 分区期间,各节点独立工作,之后同步
| 组合 | 放弃 | 典型系统 | 场景 |
|---|---|---|---|
| CP | 可用性 | ZooKeeper、HBase、etcd | 配置中心、分布式锁 |
| AP | 一致性 | Cassandra、DynamoDB | 社交、购物车 |
| CA | 分区容忍 | 单机数据库 | 现实中不存在(分布式就必有分区) |
注意:CA 组合在分布式环境下不存在——因为只要系统是分布式的,网络分区就可能发生,就必须在 C 和 A 之间做选择。(CA 只存在于”单机系统”里,但单机系统根本不是分布式系统。)
2. 一致性模型:从最强到最弱
CAP 里的 C(一致性)实际上是一个”程度”问题。业界定义了从强到弱的多种一致性模型:
| 级别 | 说明 | 典型场景 |
|---|---|---|
| 强一致性 | 写入后,任何节点立刻能读到 | 关系数据库单机、银行 |
| 顺序一致性 | 所有节点看到相同操作顺序 | ZooKeeper、etcd |
| 因果一致性 | 有因果关系的操作保持顺序 | 分布式社交系统 |
| 最终一致性 | 经过一段时间,所有节点一致 | DNS、DynamoDB |
| 读己之写 | 自己写入的,自己立刻能读到 | 个人笔记同步 |
2.1 最终一致性的四种变体
实际工程中最常用的是”最终一致性”,它有几种变体,满足程度不同:
| 变体 | 保证 | 举例 |
|---|---|---|
| 读己之写一致性 | 写入者自己立刻能读到自己的写入 | 发朋友圈后立刻能看到 |
| 会话一致性 | 同一会话内保证一致 | 同一浏览器会话内 |
| 单调读一致性 | 读到的数据不会”倒退”(不会先看到新数据又看到旧数据) | 翻页加载 |
| 单调写一致性 | 写操作按顺序执行 | 消息按顺序落库 |
一句话理解:最终一致性 = “最终会一致,中间过程可能不一致”。“最终”多快?取决于系统设计(几毫秒到几秒不等)。
3. BASE 理论:最终一致性的实践哲学
3.1 BASE vs ACID
传统关系数据库强调 ACID(原子性、一致性、隔离性、持久性)。互联网高并发系统追求可用性,提出 BASE:
| 维度 | ACID | BASE |
|---|---|---|
| 一致性 | 强一致 | 最终一致 |
| 可用性 | 可能牺牲 | 优先保证 |
| 隔离性 | 严格隔离 | 宽松隔离 |
| 典型场景 | 金融交易 | 互联网应用 |
| 哲学 | 宁可失败,不可出错 | 宁可暂时出错,不可不可用 |
3.2 BASE 三要素
| 要素 | 含义 | 例子 |
|---|---|---|
| Basically Available(基本可用) | 系统大部分时间可用,极端时可降级 | 大促时关闭非核心功能 |
| Soft State(软状态) | 允许系统存在中间状态 | 数据同步中,各节点不同 |
| Eventually Consistent(最终一致) | 经过一段时间,数据最终一致 | 点赞数几秒后同步 |
BASE 不是”放弃一致性”,而是”接受最终一致,换取高可用”——最终结果还是一致的,只是”中间过程”允许不一致。
3.3 为什么互联网系统偏爱 BASE
- 用户无法接受”打不开”:购物车打不开比”数量稍滞后”严重得多
- 规模效应:系统越大,网络分区概率越高,强一致的成本越高
- 业务容忍度:很多业务(点赞、浏览数、购物车)对”几秒延迟”无感
但:金融、库存这类业务不能 BASE——库存错了会超卖,资金错了会出事。一致性强度必须匹配业务风险。
4. 分布式共识:如何在节点间达成一致
4.1 为什么需要共识
当系统选择”强一致”(CP)时,多个节点必须就”某个值是什么”达成一致——比如”谁是主节点""这个操作是否提交”。共识算法(Consensus Algorithm)就是让多个节点在不可靠网络上达成一致的数学方法。
4.2 主要共识算法
| 算法 | 类型 | 容错能力 | 典型应用 |
|---|---|---|---|
| Paxos | 理论奠基 | 少数派故障可容忍 | 分布式理论基础 |
| Raft | 实用(易理解) | 少数派故障可容忍 | etcd、Consul |
| ZAB | 实用 | 少数派故障可容忍 | ZooKeeper |
容错能力:三者都能容忍少于一半的节点故障。即 n 个节点,最多允许 个故障——5 个节点最多坏 2 个,3 个节点最多坏 1 个。
4.3 Raft 算法:最容易理解的共识算法
Raft 把共识问题拆成三个子问题:领导者选举、日志复制、安全性。
角色划分:
Leader(领导者): 处理所有写请求,复制日志
Follower(跟随者): 被动响应 Leader
Candidate(候选者): 选举时的临时角色
领导者选举:
1. Follower 等待超时(随机 150-300ms)没收到 Leader 心跳
→ 自认为 Leader 挂了,变成 Candidate
2. Candidate 给自己投票,并向其他节点请求投票
3. 获得多数票(n/2+1)→ 成为新 Leader
4. 新 Leader 开始发送心跳,其他节点回到 Follower
日志复制:
1. 客户端写请求 → 到达 Leader
2. Leader 把日志追加到本地 → 并行复制到所有 Follower
3. 多数节点确认 → Leader 提交日志(真正生效)
4. 提交后通知所有节点应用该日志
核心保证:只有 Leader 能提交,提交前必须获得多数确认——这保证了即使部分节点故障,已提交的日志也不会丢失。
5. 分布式系统的一致性实践选择
5.1 业务场景 → 一致性选择
| 场景 | 一致性选择 | 原因 |
|---|---|---|
| 金融交易、支付 | 强一致(CP) | 资金安全不可妥协 |
| 库存扣减 | 强一致 | 超卖是灾难 |
| 分布式锁、配置中心 | 强一致(CP,etcd/ZooKeeper) | 配置不一致会导致系统行为混乱 |
| 社交动态、点赞数 | 最终一致(AP) | 用户体验优先,延迟可接受 |
| 购物车 | 最终一致 | 用户能容忍”稍后同步” |
| 订单状态 | 通常强一致 | 状态机混乱会造成业务错误 |
5.2 实践中的”混合”智慧
实际系统往往不是”全 CP”或”全 AP”,而是混合——不同数据用不同策略:
一个电商系统:
库存、余额 → 强一致(Redis/DB 事务)
商品详情 → 最终一致(缓存,可容忍短暂旧数据)
用户行为数据 → 最终一致(异步上报)
支付状态 → 强一致(支付回调 + 对账)
关键认知:一致性策略不是”整个系统一个选择”,而是按数据、按业务,选择各自合适的一致性。
5.3 选择一致性的决策框架
遇到一个数据一致性决策时,问三个问题:
- 不一致会怎样? 会造成资金损失/超卖/法律风险 → 强一致;只是体验略差 → 最终一致
- 能容忍多长时间的延迟? 毫秒级 → 强一致;秒级甚至更长 → 最终一致
- 高可用优先级多高? 分区时”拒绝服务”的代价 vs “短暂不一致”的代价,哪个更大?
6. 实战中的”最终一致”落地
选了最终一致性之后,怎么保证”最终能一致”?三种经典方案:
6.1 本地消息表 + 定时任务
业务操作 + 写消息表 在同一个本地事务
→ 定时任务扫描消息表,把未发送的消息投递出去
→ 消费者处理成功 → 标记已处理
6.2 消息队列(可靠投递)
业务操作成功 → 发送消息到 MQ
→ 消费者消费,处理成功 → ACK
→ 处理失败 → 重试 / 死信队列
6.3 Saga / 补偿事务
跨服务的多步操作,每一步带补偿(见《微服务架构》Saga 模式)。
共同要点:
- 幂等处理(消息可能重复投递,消费必须幂等)
- 对账机制(定期核对,发现不一致就修复)
- 最终收敛(通过重试/补偿,系统最终达到一致)
7. 常见误区
误区一:CAP 是”三选二”
真相:P(分区容忍)不是可选,是必然。真正的问题是”分区发生时选 C 还是选 A”。 CA 组合在分布式系统中不存在。
误区二:最终一致 = 可以永远不一致
真相:“最终”意味着”经过一定时间后收敛”。系统必须通过重试、补偿、对账等机制保证最终一致——“最终一致”是承诺,不是放任。
误区三:强一致一定比最终一致好
真相:强一致的代价是可用性降低和性能开销。一致性选择必须匹配业务:银行要强一致,社交 App 用最终一致是合理的。
误区四:最终一致性 = 用户能看到错数据
真相:虽然数据短暂不一致,但大多数场景通过”读己之写”等变体,让用户本人看不到异常。最终一致性设计得好,用户体验是无感的。
误区五:共识算法 = 强一致的一切
真相:共识算法(Raft/Paxos)解决的是”节点间对单个值达成一致”,但分布式系统的一致性远不止共识——还有事务、消息、缓存同步等更广的范畴。
小结
- 初学者要点:CAP 的正确打开方式是”分区发生时,保 C 还是保 A”——P 是必选项; 最终一致性不是”可以不一致”,而是用重试、补偿、对账保证最终收敛; 一致性强度按数据分:库存资金强一致,点赞浏览最终一致。
- 进阶注意:CAP 是网络分区的二选一,不是日常运行的三选二——无分区时 C 与 A 可以兼得,代价是延迟;Raft 的多数派提交保证了已提交日志不丢,但它只解决复制 不解决业务语义;设计跨服务流程时,Saga 的补偿与幂等才是最终一致性的工程落点。