前置知识: 软件工程与测试

CAP 理论与最终一致性

10 min高级

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:

维度ACIDBASE
一致性强一致最终一致
可用性可能牺牲优先保证
隔离性严格隔离宽松隔离
典型场景金融交易互联网应用
哲学宁可失败,不可出错宁可暂时出错,不可不可用

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 个节点,最多允许 ⌊(n−1)/2⌋\lfloor (n-1)/2 \rfloor 个故障——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 选择一致性的决策框架

遇到一个数据一致性决策时,问三个问题:

  1. 不一致会怎样? 会造成资金损失/超卖/法律风险 → 强一致;只是体验略差 → 最终一致
  2. 能容忍多长时间的延迟? 毫秒级 → 强一致;秒级甚至更长 → 最终一致
  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 的补偿与幂等才是最终一致性的工程落点。