跳到内容

第 7 卷 / 第 1 章 / 第 2 课

1.2 时间、因果关系与一致性模型 ​

物理时间适合回答“现实世界大约几点发生”,不能单独证明两个分布式事件的因果顺序。逻辑时间表达因果或版本,一致性模型则规定操作在客户端眼中允许呈现哪些历史。

墙上时钟与单调时钟 ​

操作系统通常提供两类时间:

  • wall clock:对应日历时间,会被 NTP 校正,可能跳变;
  • monotonic clock:用于同一时钟域内的持续时间测量,不应倒退;能否跨进程比较、是否计入系统挂起时间,取决于具体 API。

超时和延迟统计使用单调时钟;审计和跨系统对齐使用标明 UTC 的时间戳,并记录时钟不确定性。不同机器的单调时钟读数不能直接相减。物理时间戳可以用于 last-write-wins 冲突规则,但误差可能让真实发生较早的写入胜出,不能把它当作因果证明。

Happened-before 是偏序 ​

Lamport 定义:

  • 同一进程中,前一个事件 happened-before 后一个事件;
  • 发送消息 happened-before 接收该消息;
  • 关系具有传递性。

若 A 和 B 之间都无法推出 happened-before,它们是并发事件。这不表示物理上同一瞬间,而是所建模的进程顺序和消息关系中不存在这条因果链。若追踪漏了消息或系统外通信,仅凭日志缺失不能断定并发。

Lamport Clock 保留因果的必要方向 ​

每个节点维护递增计数器:本地事件(包括发送)先加一,发送时携带该值;接收事件则设为 max(local, received) + 1。

它保证:

text
A happened-before B  ⇒  L(A) < L(B)

逆命题不成立。看到 L(A) < L(B) 不能断定 A 导致 B;并发事件也可能得到不同 Lamport 时间戳。因此 Lamport Clock 可建立与因果兼容的排序,但不能直接检测并发。

若需要全序,可再使用节点 ID 打破相同逻辑时间的并列;这个全序是协议约定,不是新的因果事实。

Vector Clock 可以识别并发 ​

在固定参与者集合中,每个节点保存一份向量,每个参与者对应一个分量。本地事件和发送事件递增自己的分量;接收时先逐分量取本地与消息向量的最大值,再递增自己的分量。若 V(A) 的每个分量都不大于 V(B),且至少一个更小,则 A happened-before B;若两向量互不支配,则事件并发。

例如 A 的第一次发送携带 [1, 0]。B 在接收之前独立发生两次事件,向量为 [0, 2]:这两个向量互不支配,说明事件并发。B 随后收到消息,取最大值再增加自己的分量,得到 [1, 3],这时发送事件确实 happened-before 接收事件。对应的 Lamport 值可为 1、2、3;单看前两个标量仍不能识别并发。

代价是元数据随参与者增长。实际系统可能使用版本向量、dotted version vector 或按副本集合压缩。动态、大规模客户端不能直接维护无限向量。裁剪或压缩后的实现还要单独说明保留了哪些因果判断能力。

Hybrid Logical Clock 组合近似时间与因果 ​

HLC 结合物理时间部分与逻辑计数。节点接收消息时会合并本地物理时间、本地 HLC 和消息 HLC;逻辑计数用于区分同一时间部分内的事件,并保留 happened-before 的时间戳递增性质。它不消除物理钟误差,也不自动产生线性一致性。时间戳与 wall clock 的接近程度依赖时钟误差假设,远端时钟跑到未来也会影响接收方。

TrueTime 显式返回物理时间不确定区间。Spanner 把它与事务并发控制、复制协议及 commit wait 结合,在默认的可串行化隔离下实现外部一致性,即事务串行顺序也尊重真实时间先后。单独拿到一个时间区间不会产生这个保证;等待到提交时间戳已确定过去,是协议的一部分。

一致性模型是一组允许历史 ​

Linearizability ​

每个操作仿佛在调用与返回之间某个瞬间原子生效;一个操作返回后才发起的另一个操作,必须排在它之后。它适合需要单对象实时顺序的场景,但多个线性化读写不会自动组成事务。例如两位客户端都读到“用户名不存在”后分别创建用户,仍可能冲突;唯一性还需要原子条件写或相应事务约束。

Sequential consistency ​

存在一个包含所有操作的总序,且每个客户端的程序顺序被保留,但不要求尊重不同客户端观察到的真实时间先后。

设 x 初值为 0,客户端 A 的 write(x, 1) 已返回,客户端 B 才调用 read(x) 并读到 0。若各客户端只有这一次操作,顺序一致性可以把 B 的读排在 A 的写之前;线性一致性不允许这样解释。

Causal consistency ​

所有节点以相同顺序观察具有因果关系的操作;无因果关系的并发操作可以顺序不同。

Eventual consistency ​

当没有新更新,且更新最终能传播到相关副本时,副本最终收敛。永久分区下不能凭这个名字期待收敛。这个定义本身不说明收敛多久、冲突如何解决、能否读到自己的写。

客户端常还需要会话保证:read-your-writes、monotonic reads、monotonic writes 和 writes-follow-reads。

Quorum 公式不是线性一致性证明 ​

在同一固定集合的 N 个副本中,成功读写分别收到 R、W 个不同副本的应答时,R + W > N 保证读写集合相交,W > N/2 保证两个写集合相交。例如 N=3、R=W=2,任意两个这样的集合至少共用一个副本。若替补节点形成的 sloppy quorum 不再取自这同一集合,就不能直接套公式。集合相交仍没有解决并发写、部分失败的写入、版本选择和读取协议,因此不能单凭它证明线性一致性。

像 Cassandra 的 ALL、QUORUM 是副本确认级别,不能仅凭级别名称宣称普通读写具有线性一致性。需要线性化条件更新时,应使用产品明确提供的共识/LWT 等机制,并核对其具体保证。

根据不变量选择保证 ​

需求可能需要
唯一用户名线性化条件写或单一权威所有者
用户刚修改后立刻看到read-your-writes 会话保证
协同编辑中的因果操作因果元数据或专门协同算法
统计与推荐可接受延迟的最终一致投影
审计时间线物理时间 + 因果/序列元数据

先写业务异常是否可接受,再选择存储和协议,不能从产品宣传的“一致性”一词反推需求。

参考资料 ​

Built with VitePress | Software Systems Atlas