mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1358 字
4 分钟
一致性级别:从强一致到最终一致
2025-01-15

你在 DynamoDB 上读取一条商品库存记录,返回”有货”,但下单时却提示库存不足。这不是 Bug,而是一致性级别的选择问题——DynamoDB 默认提供最终一致性读,你读到的可能是过期数据。分布式系统中,一致性不是非黑即白的选择,而是一个从强到弱的光谱。

一、为什么需要不同的一致性级别#

强一致性保证读到最新数据,但代价是更高的延迟和更低的可用性。弱一致性牺牲数据实时性,换取更好的性能和可用性。

不同业务对一致性的需求差异巨大:

业务一致性需求原因
银行转账强一致余额错误不可接受
购物车最终一致短暂不一致可容忍
社交动态因果一致自己的评论必须立即可见
DNS 查询最终一致域名变更传播需要时间

二、一致性级别光谱#

从强到弱,主要的一致性级别如下:

graph LR A[线性一致性] --> B[顺序一致性] B --> C[因果一致性] C --> D[最终一致性]

2.1 线性一致性(Linearizability)#

线性一致性是最强的单对象一致性模型,也称为强一致性:

  • 每个操作看起来在某个时间点瞬间生效
  • 所有操作按真实时间排序,读操作总是读到最近写入的值
  • 一旦写操作完成,所有后续读操作都能看到该值
时间线:
t1: Write(x=1) ────────────────── 完成
t2: Read(x) → 返回 1 (t2 > t1,必须读到新值)
t3: Read(x) → 返回 1
Info

线性一致性要求操作看起来在调用和响应之间的某个时刻原子地生效。这并不意味着操作是瞬时的,而是外部观察者无法区分操作是在哪个时刻生效的。

典型实现:ZooKeeper 的写操作、etcd 的默认读操作。

2.2 顺序一致性(Sequential Consistency)#

顺序一致性弱于线性一致性:

  • 所有进程看到的操作顺序相同
  • 每个进程自己的操作按程序顺序出现
  • 但不要求操作按真实时间排序
进程 P1: Write(x=1) Write(x=2)
进程 P2: Read(x)→2 Read(x)→1
顺序一致性允许:P2 先读到 x=2,再读到 x=1(只要所有进程看到相同顺序)
线性一致性不允许:x=2 在 x=1 之后写入,后续读不可能返回旧值

2.3 因果一致性(Causal Consistency)#

因果一致性只保证有因果关系的操作按顺序执行:

  • 有因果关系的操作(如”先写后读”)所有进程看到相同顺序
  • 无因果关系的并发操作可以不同进程看到不同顺序
进程 P1: Write(x=1)
进程 P2: Read(x)→1, Write(y=2) (y=2 因果依赖于 x=1)
进程 P3: Read(y)→2, Read(x)→1 (因果顺序保持)
进程 P4: Read(y)→2, Read(x)→0 (违反因果:y 依赖 x,x 必须先更新)

典型实现:Cassandra 的 LOCAL_QUORUM 读。

2.4 最终一致性(Eventual Consistency)#

最终一致性是最弱的一致性保证:

  • 不保证立即读到最新值
  • 如果没有新的写入,最终所有副本会收敛到相同值
  • “最终”是多久没有保证
t1: Write(x=1) → Node1
t2: Read(x) → Node2 → 返回 0(旧值,但最终会更新为 1)
t3: Read(x) → Node2 → 返回 1(最终一致)

典型实现:DynamoDB 的最终一致性读、DNS 系统。

三、一致性级别的形式化比较#

级别实时约束因果约束全序约束性能可用性
线性一致性最低最低
顺序一致性较低较低
因果一致性较高较高
最终一致性最高最高

四、可调一致性模型#

许多系统允许在读写操作级别调整一致性强度。

4.1 DynamoDB#

# 强一致性读
table.get_item(Key={"id": "123"}, ConsistentRead=True)
# 最终一致性读(默认,消耗一半读容量单位)
table.get_item(Key={"id": "123"})

4.2 Cassandra#

Cassandra 使用 W + R > N 公式控制一致性:

  • N:副本数
  • W:写入确认数
  • R:读取确认数
N=3, W=2, R=2 → 强一致(2+2>3)
N=3, W=1, R=1 → 弱一致(1+1<3)
N=3, W=2, R=1 → 中等一致
-- 强一致性读
SELECT * FROM users WHERE id = 123 CONSISTENCY QUORUM;
-- 最终一致性读
SELECT * FROM users WHERE id = 123 CONSISTENCY ONE;

4.3 MongoDB#

// 强一致性读(读主节点)
db.users.find({id: 123}).readPref("primary")
// 最终一致性读(读从节点)
db.users.find({id: 123}).readPref("secondaryPreferred")

五、一致性级别的选型实践#

5.1 决策流程#

  1. 确认业务是否允许读到过期数据
  2. 评估过期数据可能造成的业务影响
  3. 测量强一致性读的延迟影响
  4. 在一致性和性能之间找到平衡点

5.2 常见场景推荐#

场景推荐级别原因
金融交易线性一致金额错误不可接受
库存扣减线性一致超卖问题严重
用户资料因果一致自己的修改立即可见
商品浏览最终一致短暂过期可容忍
日志收集最终一致顺序无关紧要
会话状态因果一致会话内操作有序

5.3 混合策略#

同一系统内不同操作可以使用不同一致性级别:

func ReadInventory(itemID string) {
if isHighValueItem(itemID) {
// 高价值商品:强一致性读
readWithConsistency(STRONG)
} else {
// 普通商品:最终一致性读
readWithConsistency(EVENTUAL)
}
}

六、常见误区#

6.1 “最终一致性等于没有一致性”#

最终一致性并非没有保证,而是保证在没有新写入的情况下数据最终收敛。关键在于”最终”的时间窗口是否在业务可接受范围内。

6.2 “强一致性总是更好的选择”#

强一致性带来更高的延迟和更低的可用性。在跨区域部署场景下,强一致性读的延迟可能达到数百毫秒,对用户体验影响显著。

6.3 “CAP 中的 C 等于线性一致性”#

CAP 定理中的 C 指的是线性一致性,但分布式系统中还有多种较弱的一致性模型可以选择,不必在 C 和 A 之间做非此即彼的选择。

一致性级别是分布式系统设计的核心决策之一。理解从线性一致性到最终一致性的光谱,以及每种级别的保证和代价,才能根据业务需求做出合理选择。

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

一致性级别:从强一致到最终一致
https://blog.souloss.com/posts/distributed/distributed-consistency-levels/
作者
Souloss
发布于
2025-01-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时