16.7 一致性模型谱系图
一句话
一致性模型不是「强弱排行榜」,而是几组正交的约束(实时序、全序、事务原子性、客户端会话);把它们画成谱系,选型就变成「业务需要哪几条约束、愿意为哪几条付 RTT 与可用性」。
谱系图:单对象的强弱链
严格一致(不可实现) 物理时间 + 瞬时全局可见
│ 削弱:物理时间 → 实时先后 + 逻辑全序
线性一致(16.2) 实时顺序 + 全序 + 单对象原子
│ 削弱:去掉实时顺序
顺序一致(16.3) 全序 + 程序顺序
│ 削弱:全序 → 仅因果顺序
因果一致(16.4,+ 收敛) 只约束 happened-before
│ 削弱:连因果顺序也不要
最终一致(16.5) 只承诺最终收敛
三条横向的补充轴,不在这条主链上:
| 轴 | 内容 | 说明 |
|---|---|---|
| 事务轴 | 可串行化 / 严格可串行化 | 约束的是「一组操作」而非「单个操作」 |
| 客户端轴 | 会话保证:16.6 | 只约束一个客户端会话,可与任意数据层组合 |
| 拓扑轴 | 单主 / 多主 / 无主、同步 / 异步 | 决定「怎么实现」,不直接等于某个模型(18.1 复制模型:单主 / 多主 / 无主) |
包含关系:谁能推出谁
| 关系 | 成立? | 理由 / 反例 |
|---|---|---|
| 严格一致 ⇒ 线性一致 | ✅ | 物理时间排序是实时顺序的特例 |
| 线性一致 ⇒ 顺序一致 | ✅ | 实时顺序的全序必然尊重程序顺序 |
| 顺序一致 ⇒ 因果一致 | ✅ | 全序是因果序的线性扩展(单对象) |
| 因果一致 ⇒ 最终一致 | ✅(加可靠投递 + 收敛) | 所有写最终可见,且并发写需确定性合并 |
| 线性一致 ⇒ 严格一致 | ❌ | 并发写可被排成任意顺序,不按物理时间(16.1) |
| 顺序一致 ⇒ 线性一致 | ❌ | 写完成后的读仍可读到旧值(H2) |
| 因果一致 ⇒ 顺序一致 | ❌ | 并发写在副本间顺序不同(R2 的 c1 在 a1 前) |
| 最终一致 ⇒ 因果一致 | ❌ | 允许「评论先于帖子」的因果气泡 |
⚠️ 上表的「顺序一致 ⇒ 因果一致」限定在单个对象/单一广播域。跨对象时顺序一致本身不可组合(16.3 的 X/Y 反例),谱系图不能直接套到「多个对象拼起来」的系统上。
一张表验证「严格包含」
| 模型 | 实时序 | 全序 | 因果序 | 分区期间可用 | 可实现 |
|---|---|---|---|---|---|
| 严格一致 | ✅(物理时间) | ✅ | ✅ | ❌ | ❌ |
| 线性一致 | ✅ | ✅ | ✅ | ❌(CP) | ✅ |
| 顺序一致 | ❌ | ✅ | ✅ | ⚠️(写不可用,读可陈旧) | ✅ |
| 因果一致 | ❌ | ❌ | ✅ | ✅ | ✅ |
| 最终一致 | ❌ | ❌ | ❌ | ✅ | ✅ |
「分区期间可用」这一列是 17.3 分区期间的取舍 的内容——这里只看结论:越往下越可用。
另一个轴:单对象 vs 事务
把「是否要求实时顺序」与「约束单个操作还是整组事务」交叉,得到四象限:
| 单个对象/操作 | 一组事务 | |
|---|---|---|
| 不要求实时 | 顺序一致 | 可串行化 |
| 要求实时 | 线性一致 | 严格可串行化 |
四个格子的含义:
| 模型 | 一句话 | 典型问题 |
|---|---|---|
| 顺序一致 | 所有操作有全序,但可以落后于现实 | 读到旧值 |
| 线性一致 | 单对象看起来像单副本,且尊重实时 | 跨 key 不一致 |
| 可串行化 | 事务等价于某个串行顺序,但不尊重实时 | 事务被排到后面,读到「未来」 |
| 严格可串行化 | 可串行化 + 事务间实时顺序 | 跨地域提交延迟(commit-wait) |
最容易混的两组:
- 线性一致 ≠ 可串行化:前者是单对象原子性,后者是事务隔离;
- 严格可串行化 ≠ 严格一致:前者可实现(Spanner 用 TrueTime),后者不可实现。
决策表:从业务不变量出发
| 业务不变量 | 需要的最弱模型 | 典型实现 |
|---|---|---|
| 余额不能透支、转账不能凭空多钱 | 严格可串行化(或线性一致 + 幂等) | Spanner、单主事务库 + 幂等(25.4 幂等设计与去重) |
| 库存不能超卖 | 线性一致 CAS / 事务 | 单主 + 条件更新、分布式锁(48.3 分布式锁) |
| 配置/元数据读到的必须是最新已提交 | 线性一致读 | etcd 默认读、ZooKeeper + sync |
| 选主、租约、分布式锁 | 线性一致 | 共识组(21.5 Raft:选主) |
| 跨对象的业务规则(订单 + 库存 + 账务) | 严格可串行化 | 分布式事务/单库事务(24.9 事务模型选型) |
| 评论不能先于帖子、回复不能先于原消息 | 因果一致 | 因果广播/会话令牌(16.4) |
| 用户刚保存的资料刷新后还在 | 会话保证(RYW) | 会话令牌/写后读主(16.6) |
| 点赞数、播放量最终准确即可 | 最终一致(CRDT) | G-Counter + 反熵 |
| 购物车、收藏夹 | 最终一致 + 会话保证 | OR-Set/多值合并 |
| 日志、分析、推荐特征 | 最终一致 | 异步管道 |
🔑 选型顺序:先写业务不变量 → 再找能保证它的最弱模型 → 最后才看系统能不能实现。反过来(先选技术栈,再解释它能给什么一致性)是绝大多数一致性事故的起点。
常见系统的定位(带前提)
| 系统 | 提供什么 | 前提 / 注意 |
|---|---|---|
| etcd | 默认线性一致读写;serializable 读是顺序一致 | 跨地域写要等多数派 RTT |
| ZooKeeper | 写线性一致、客户端 FIFO 顺序、读可能陈旧 | 需要新鲜读要先 sync() |
| Spanner | 严格可串行化 | TrueTime + commit-wait,提交有延迟 |
| Cassandra | 以最终一致为主;分区内 LWT 提供 CAS | quorum 读不等于线性一致(16.2 的坑) |
| DynamoDB | 最终一致读/强一致读可选;事务 API | 强一致读仍受分区可用性约束 |
| MongoDB | 读/写关注可调;因果会话 | 因果保证限于会话 |
| Redis | 单节点单命令原子;主从异步复制 | 切主可能丢写,不能当线性一致用 |
| Kafka | 分区内日志顺序 + ISR | 跨分区没有全局顺序 |
⚠️ 这张表只能当「起点」:同一个产品的不同 API、不同配置提供不同模型。宣称一致性之前,先看 API 语义与故障行为,而不是看产品名。
六个常见误解
| 误解 | 现实 |
|---|---|
| 「CAP 三选二」 | 分区发生时只能在 C 与 A 之间取舍;P 不是可选项(17.2 为什么 “三选二” 是错的) |
| 「最终一致 = 便宜」 | 冲突解决、元数据、反熵、修复、监控都要钱;省的是「协调等待」,不是总成本 |
| 「线性一致 = ACID」 | 线性一致是单对象可见性;ACID 是事务属性,两者正交 |
| 「强一致靠 NTP 就行」 | 时钟有误差与回拨;线性一致靠共识/租约,不靠时钟同步(16.1) |
| 「因果一致很弱,没什么用」 | 在始终可用的系统里它是能实现的最强模型(16.4) |
| 「顺序一致 = 线性一致」 | 差一条实时顺序;本地读的全序广播系统是顺序一致,不是线性一致 |
反模式
| 反模式 | 后果 |
|---|---|
| 用「强一致」当需求描述 | 无法验收,团队各自理解不同 |
| 只看产品名,不看 API 与配置 | 同一个库的两种读模式,一致性差一个等级 |
| 把会话保证当全局一致 | 跨用户读到旧值,投诉时才发现 |
| 跨对象用线性一致对象拼事务 | 组合后不满足原子性(16.3 的不可组合性) |
| 为「未来可能的需求」上严格可串行化 | 跨地域延迟与成本翻倍,收益为零 |
| 把最终一致当「不用设计冲突」 | 冲突在峰值出现,LWW 静默丢数据 |
| 忽略故障下的一致性 | 正常路径全绿,切主/分区后读出旧值或丢写 |
自检
Q1 用一张图说明:为什么「线性一致 ⇒ 顺序一致」成立,而反过来不成立?
▸ 答案:线性一致要求存在一个尊重实时顺序的全序;实时顺序包含程序顺序,所以这个全序也尊重程序顺序,满足顺序一致的定义。反过来,顺序一致的全序不要求尊重实时顺序,可以把「写完成后才发起的读」排在写之前,于是读到旧值——H2 就是反例。差别只在「实时顺序」这一条约束。
Q2 「线性一致」与「可串行化」分别在四象限的哪个位置?为什么不能互相替代?
▸ 答案:线性一致在「单对象 + 要求实时」;可串行化在「事务 + 不要求实时」。线性一致不给跨对象原子性(读 x、读 y 可能来自不同时刻);可串行化不给实时顺序(事务可以被排到实时更晚的位置)。要同时满足,选「严格可串行化」。工程上银行转账需要的是后者,单个配置读取需要的是前者。
Q3 因果一致比顺序一致弱,为什么很多全球系统反而选它?
▸ 答案:因为顺序一致要求所有副本共享同一个全序,这需要全序广播/共识;分区时少数派不能继续写,跨地域写要付 RTT。因果一致只约束有因果关系的操作,并发写各副本可以不同顺序,因此分区期间两侧都能继续读写,网络恢复后靠反熵收敛。对社交、协作、购物车这类业务,「不出现因果气泡 + 最终收敛」已经够用,且可用性高一个等级。
Q4 会话保证在谱系图的哪里?它能提升全局一致性等级吗?
▸ 答案:它不在主链上,是一条「客户端轴」:只约束一个会话内的四条性质(RYW/MR/MW/WFR),可以叠加在最终一致的数据层上。它不改变全局模型——其他客户端仍可能读到旧值,跨客户端的因果也不保证。它的价值是「让单个用户不看到自己的操作倒退」,成本远低于全局强一致。
Q5 业务说「我们要强一致」,你怎么把它翻译成可验收的规格?
▸ 答案:追问三个维度并写成 API 级承诺:① 范围:单 key 还是跨 key 事务?② 实时性:写返回后,其他节点的读是否必须立刻看到(线性一致),还是允许会话内保证即可(RYW)?③ 故障行为:分区/切主时,读是否可以返回旧值、写是否可以失败?把答案落成「某 API 在某故障模型下提供线性一致读/严格可串行化事务」,再配故障注入测试(切主、分区、时钟偏移),才能验收。
跨篇相关:谱系顶点的不可实现性见 16.1 严格一致性;实时约束与协调成本见 16.2 线性一致性;去掉实时约束见 16.3 顺序一致性;因果与可用性见 16.4 因果一致性;最终收敛与冲突见 16.5 最终一致性;客户端轴见 16.6 会话保证;CAP 与 PACELC 的完整讨论见 17.1 CAP 的由来与准确表述、17.2 为什么 “三选二” 是错的、17.4 PACELC:无分区时的延迟-一致性权衡、17.6 CAP 之后的补充理论;共识与 quorum 见 21.2 Quorum 与 W + R > N 的证明、21.5 Raft:选主;复制的同步/异步见 18.2 同步、半同步、异步;逻辑时钟与因果见 23.3 Lamport 逻辑时钟、23.4 向量时钟;事务模型选型见 24.9 事务模型选型;分布式锁见 48.3 分布式锁。