跳到内容

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

分布式群岛的两座钟楼总有细微偏差,一封先写后读的信也可能以相反顺序抵达不同观察者。

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

墙上时钟与单调时钟

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

  • wall clock:对应日历时间,会被 NTP 校正,可能跳变;
  • monotonic clock:只用于测量当前进程内的持续时间,不应倒退。

超时和延迟统计使用单调时钟;审计和跨系统对齐使用带时区的 UTC 时间戳,并记录时钟不确定性。不要用两台机器的毫秒时间戳决定并发写入胜负。

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;若两向量互不支配,则事件并发。

代价是元数据随参与者增长。实际系统可能使用版本向量、dotted version vector 或按副本集合压缩。动态、大规模客户端不能直接维护无限向量。

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

HLC 把物理时间部分与逻辑计数结合,使时间戳接近 wall clock,同时在时钟回退或同一物理时间内保持逻辑单调。它不消除物理钟误差,也不自动产生线性一致性;它只是更实用的排序和版本基础。

像 TrueTime 这样的系统显式返回物理时间不确定区间,并通过等待等协议措施建立外部一致性。关键不是“钟特别准”,而是误差被测量并进入正确性协议。

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

Linearizability

每个操作仿佛在调用与返回之间某个瞬间原子生效,并尊重真实时间先后。适合锁、主节点、余额和唯一约束等需要单对象最新语义的场景。

Sequential consistency

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

Causal consistency

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

Eventual consistency

当没有新更新时,副本最终收敛。这个定义本身不说明收敛多久、冲突如何解决、会不会读到自己的写。

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

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

在 N 个副本中,R + W > NW > N/2 可以让读写 quorum 相交,但仍需处理并发写、版本选择、失败重试、读修复和客户端协调。

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

根据不变量选择保证

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

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

参考资料

Built with VitePress | Software Systems Atlas