【分布式系统百科】一致性模型全景:从线性一致性到最终一致性的光谱

💡 原文中文,约18500字,阅读约需45分钟。
📝

内容提要

文章辨析分布式数据一致性模型:线性一致性要求合法全序并保持跨进程实时序;顺序一致性只保持进程内程序序,无法逐键验证;因果一致性要求结果不越过原因,但并发冲突仍需额外收敛规则;最终一致性只是收敛承诺,有限历史无法证明。会话保证与前缀可见性各有作用范围。文中对照ZooKeeper、Spanner、DynamoDB、Cassandra的接口承诺,并给出Go检查器,强调应按业务不变量和故障条件选择保证,而非贴“强一致”标签。

🔎

延伸解读

一致性模型不是一条强弱直线

文章强调,线性、顺序、因果、最终一致等模型并非从强到弱的单一阶梯。它们分别约束读写排序、会话可见性、最终收敛和事务隔离,需要分开评估。放松排序也不自动带来延迟或可用性收益,具体取决于协议和拓扑。因此,选型时应按业务不变量和故障条件选择保证,而非简单贴“强一致”标签。

产品承诺要看具体接口

文章对照了 ZooKeeper、Spanner、DynamoDB 和 Cassandra 的接口承诺。例如,ZooKeeper 的普通读可能读到旧值,sync 后读也不保证线性一致;DynamoDB 的强读仅适用于表和 LSI,GSI 和 Streams 只支持最终一致;Cassandra 的 quorum 读写保证交集,但不等于线性一致。因此,不能给产品贴单一标签,而应核对具体 API 和复制模式。

有限历史无法证明最终一致

文章指出,最终一致性是收敛承诺,有限的操作历史无法证明系统将来总会收敛。即使当前所有读都一致,仍可能存在未访问的落后副本;即使读到旧值,系统也可能稍后修复。因此,检查器不输出“最终一致性:PASS”,因为缺乏传播和故障前提,这样的结论没有检测意义。工程上应关注收敛机制和故障恢复,而非依赖短期测试。

从业务不变量倒推保证级别

文章建议,选型应先明确要保护的业务不变量及其范围,再选择接口。例如,单键状态机、多键交易、整次查询快照对应不同保证;谁必须看见谁的结果决定需要实时序、会话保证还是因果链;故障时愿意等待多数派、远端依赖还是允许旧值,影响延迟和可用性。强读也挡不住“先读再写”的竞争,需要原子条件更新或合适的事务隔离。

❓

Q&A

线性一致性和顺序一致性到底有什么区别?

线性一致性要求存在一个合法全序,并且保持跨进程的实时序:如果操作A在操作B开始前已经返回,那么A必须排在B之前。顺序一致性只要求保持每个进程内部的程序序,不要求跨进程的实时序。因此,顺序一致性允许不同客户端以不同顺序观察非重叠操作,而线性一致性不允许。

因果一致性要求什么?它能自动解决并发冲突吗?

因果一致性要求进程的观察尊重因果依赖,即结果不能越过原因。依赖来源包括程序序、读自关系和传递性。它不自动解决并发冲突:并发写可能被不同观察者以不同顺序看到,要获得一致的冲突解决和收敛,还需要额外的规则(如COPS中的causal+)。

最终一致性到底承诺了什么?为什么有限测试不能证明它?

最终一致性承诺:写入停止后,在更新能够继续传播、非故障副本继续运行等前提下,副本最终收敛到相同状态。它不指定收敛时间,也不定义冲突解决方式。有限历史无法证明“将来总会收敛”,因为可能存在尚未访问的落后副本,或者系统稍后修复;没有传播和故障前提,输出“通过”没有检测意义。

ZooKeeper的普通读是线性一致的吗?sync()能保证读到最新值吗?

ZooKeeper的更新提供线性一致性,但普通读通常由连接的服务器本地服务,可能读到旧值。官方描述为ordered sequential consistency (OSC(U)),整个普通读写接口不提供无条件线性一致性。sync()当前不是quorum操作,不能把sync后再读无条件当作线性一致读;它通常改善新鲜度,但在旧Leader等场景下仍可能不足。

DynamoDB的强一致读和事务分别提供什么保证?跨区复制呢?

DynamoDB表和LSI支持强一致读,反映读取之前所有成功写入,在单item读写语境中具有线性一致性的“写后再读”语义;GSI和Streams只支持最终一致读。事务API(TransactWriteItems/TransactGetItems)支持同区域跨表多item事务,提供可串行化隔离。跨区复制分MREC(异步,本区强读可能漏掉另一区写)和MRSC(支持各副本强读此前成功写入),不能笼统说全球表都是最终一致。

Cassandra的quorum读写能保证线性一致性吗?

不能。Cassandra的quorum读写只保证读写集合有交集(R+W>N),但普通读写使用timestamp和Last-Write-Wins处理冲突,不保证相交副本按真实时间保存最后一次请求的值。后发生但时间戳较小的成功写仍可能被LWW覆盖,因此不满足线性一致性。轻量事务(LWT)通过Paxos提供单分区compare-and-set的线性一致语义,但不是跨分区事务。

会话保证有哪几种?它们能代替因果一致性吗?

四种会话保证:单调读(后续读不能丢掉已观察到的更新)、单调写(同会话的写按程序序生效)、读己之写(后续读反映自己的先前写)、写跟随读(新写的可见位置不能越过其先前读所依赖的更新)。在标准读写模型、相同会话边界及一致的可见性定义下,这四种保证组合起来可以表达因果一致性所需的约束,但不能理解为“打勾就自动获得任意跨服务因果一致性”。

为什么说“强一致一定慢”这种说法太粗?

线性一致性定义不规定每个读都重新发起共识;有效租约、读屏障、单副本执行等方案的通信路径不同,是否正确要看故障模型和协议前提。跨区通信、提交等待和冲突重试都会进入延迟预算。放松保证可能去掉一些等待,也可能引入依赖检查或更复杂的冲突处理。没有拓扑、负载、故障条件和指标口径,就不能许诺固定P99或“数量级提升”。

🏷️

标签

➡️

继续阅读