计算机中的各种“一致性”
Written with Claude.
0x00 引子
我曾希望我的多活应用在进行配置更新时,能做到对外的一致性——某一时刻之后,所有节点均以新配置提供服务;如果有节点更新失败,则所有节点都继续沿用旧配置。我以为这只是一个系统的某种“特性”,实现起来应该不难。
查阅资料后,我发现这个需求牵扯到三个相互独立却又彼此依赖的概念:
- 线性一致性(Linearizability):保证外部观察者看到的行为符合“某时刻切换”的语义
- 原子提交(Atomic Commit):保证所有节点要么全部更新成功,要么全部回滚
- 共识(Consensus):让分布式节点对“是否提交”达成统一决定
这让我意识到,“一致性”在计算机不同领域里的含义差异极大。本文试图梳理这些含义,厘清它们之间的关系。
0x01 一致性在哪里出现?
“一致性”这个词在计算机领域至少出现在以下四个语境中:
1. 硬件:CPU 缓存一致性
现代多核 CPU 中,每个核心都有自己的 L1/L2 缓存。当多个核心同时读写同一内存地址时,如何保证各核心缓存中的数据不产生矛盾?这是**缓存一致性(Cache Coherence)**问题,通常由硬件协议(如 MESI)解决。
MESI 协议为每个缓存行定义四种状态:Modified、Exclusive、Shared、Invalid。当一个核心写入数据时,协议会将其他核心中该行的状态标记为 Invalid,从而强制它们下次从内存或发出写请求的核心处重新获取数据。
与缓存一致性密切相关的是**内存序(Memory Ordering)**问题。即使缓存一致,编译器和CPU 的乱序执行仍可能导致另一个线程看到“不合逻辑”的操作顺序。这催生了各语言的内存模型规范(JMM、Go Memory Model、C++ Memory Model),以及 volatile、fence、atomic 等语言原语。
伪共享(False Sharing)是一个经典陷阱:两个不相关的变量恰好位于同一缓存行,导致频繁的缓存失效,严重影响性能。
延伸阅读:
2. 数据库:ACID 中的 C
数据库事务的 ACID 中,C 代表 Consistency(一致性),但这里的含义与分布式系统中的“一致性”完全不同。ACID 的 C 是指:事务执行前后,数据库必须满足所有预定义的业务约束(如账户余额不能为负、外键引用必须有效)。
换句话说,这里的“一致性”是业务语义层面的正确性,由应用开发者定义,由数据库的AID(原子性、隔离性、持久性)保证。C 不是独立的机制,而是 AID 的结果。
单机事务的隔离性本身又是一个复杂的话题,涉及不同的隔离级别(读未提交、读已提交、可重复读、串行化)和实现机制(锁、MVCC)。不同的隔离级别对应不同的并发异常(脏读、不可重复读、幻读),这也可以视为一种“一致性模型”。
3. 分布式系统:一致性模型与共识
这是本文的重点。分布式系统中的“一致性”通常指多个节点在读写共享数据时表现出的行为规范,有一个从强到弱的谱系:
- 线性一致性(Linearizability):最强。所有操作看起来像是在某个单一时间点上原子完成的,且顺序与真实时间吻合。代价是需要协调,延迟高。
- 顺序一致性(Sequential Consistency):所有操作有一个一致的全局顺序,但这个顺序不必与真实时间完全吻合。
- 因果一致性(Causal Consistency):只要求有因果关系的操作保持顺序。
- 最终一致性(Eventual Consistency):最弱。只保证在没有新写入的情况下,所有副本最终会收敛到相同值。DNS 是经典例子。
一致性强度越强,可用性和性能的代价越大,这正是 CAP 定理和 PACELC 模型所描述的权衡。
与一致性模型相关但不同的是**共识(Consensus)**问题:让分布式节点对某个值(如“是否提交事务”、“谁是 leader”)达成统一决定,且即使部分节点宕机也能正常工作。Paxos 和Raft 是解决共识问题的两种经典算法。
Paxos 先提出(理论更优雅),Raft 后来(工程更易理解)。两者的核心思想一致:通过多数派(quorum)投票来容忍少数节点故障。
延伸阅读:
- Consistency Models - Jepsen(一致性模型全景)
- [译]简说Paxos(Paxos Made Simple) >> Ying ZHANG
- Paxos vs. Raft:共识算法的异同
- 可靠分布式系统:Paxos 的直观解释
4. 分布式数据库:两种“一致性”的交汇
分布式数据库(如 TiDB、CockroachDB、Spanner)同时面对上述两个维度的问题:
- 从数据库角度,它要提供 ACID 事务语义
- 从分布式角度,它需要在多个节点间保证线性一致性或其他一致性模型
分布式事务就是在这个交汇处诞生的:如何让跨节点的事务也满足 ACID?常见方案有:
- 2PC(两阶段提交):经典方案,有协调者单点问题,且阻塞
- Saga:将长事务拆解为一系列可补偿的短事务,适合业务层面的最终一致性
- Calvin / Deterministic DB:通过提前确定事务顺序(借助共识算法)来避免传统锁
延伸阅读:
- 分布式数据库的一致性问题与共识算法
- DDIA(《Designing Data-Intensive Applications》)第 7、9 章
5. 应用层:缓存与数据库的一致性
这是最贴近日常业务开发的场景。当应用同时使用 Redis 等缓存和 MySQL 等数据库时,如何保证两者数据不出现“缓存有、DB 没有”或“缓存旧、DB 新”的不一致?
常见策略:
- Cache Aside(旁路缓存):先更新 DB,再删除(而非更新)缓存,利用缓存重建保证最终一致
- Write Through:同步写穿,保证强一致,但写延迟高
- Write Behind(Write Back):异步写回,性能好,但有数据丢失风险
在并发场景下,即使是“先更新 DB 再删除缓存”也存在极端情况下的竞态,需要结合版本号、消息队列(Canal + MQ)等方案进一步保障。
0x02 几个关键问题
Q:ACID 的 C 和分布式的一致性有什么关系?
几乎没有。ACID 的 C 是业务正确性,由单机事务机制保证;分布式的一致性是多副本数据的可见性规范,由复制协议保证。两者用了同一个词,但含义完全不同。DDIA 的作者 Kleppmann认为 ACID 的 C 放在这里其实是“凑首字母”。
Q:共识和一致性是同一回事吗?
不是。一致性(Consistency)描述系统对外呈现的行为规范;共识(Consensus)是分布式节点之间达成协议的过程。共识是实现强一致性的一种手段,但不是唯一手段(如Zab、Viewstamped Replication 也可以实现线性一致性)。
Q:线性一致性和串行化(Serializability)有什么区别?
串行化是数据库事务隔离的最强级别(操作顺序等价于某种串行执行);线性一致性是分布式系统单个操作的实时性要求。两者结合称为严格串行化(Strict Serializability),是分布式数据库能提供的最强保证,也是代价最高的。
0x03 总结
| 语境 | 术语 | 核心问题 | 典型机制 |
|---|---|---|---|
| CPU 硬件 | 缓存一致性 | 多核缓存数据矛盾 | MESI 协议 |
| 编程语言 | 内存模型 | 指令重排序的可见范围 | fence / atomic |
| 单机数据库 | ACID-C | 业务约束不被破坏 | 约束检查 |
| 分布式系统 | 一致性模型 | 多副本数据的可见规范 | 复制协议 |
| 分布式协调 | 共识 | 节点间统一决策 | Paxos / Raft |
| 分布式数据库 | 分布式事务 | 跨节点 ACID | 2PC / Saga |
| 应用缓存 | 缓存一致性 | 缓存与 DB 数据同步 | Cache Aside |
参考资料
- Consistency Models - Jepsen
- Memory Models by Russ Cox
- LKMM(Linux-Kernel Memory Model)
- 分布式数据库的一致性问题与共识算法
- MESI 协议学习笔记
- 缓存与数据库一致性问题深度剖析
- Designing Data-Intensive Applications(DDIA)- Martin Kleppmann
- 高并发情况下如何保证金额加减的一致性 - V2EX
- # 看懂这篇,才能说了解并发底层技术
- # 并发扣款,如何保证一致性?
- A list of links
最后修改于 2026-03-22