两台服务器各写下一条日志:A 的时间是 10:00:00.120,B 的时间是 10:00:00.115。单看时间戳,B 似乎更早;如果 B 的时钟比 A 慢了 20 毫秒,真实顺序就可能相反。
在一台机器上,程序可以借助同一个时钟和执行序列判断先后。跨机器后,每个节点都有自己的时钟,消息又会经历不同延迟。此时「哪件事先发生」需要拆成两个问题:现实时间大约是多少,以及一个事件能否影响另一个事件。
Harshit Khosla 的分布式系统系列第二篇介绍了物理时钟、Lamport 时间戳、向量时钟、混合逻辑时钟和因果一致性。本文沿着同一主线,进一步区分它们能证明什么、无法证明什么,以及工程中该如何选择。
物理时钟只能给出带误差的时间
服务器的物理时钟来自本地振荡器,会随温度、硬件和运行时间产生漂移。NTP 等同步协议可以持续校正,但同步消息本身也要经过网络,往返延迟和路径变化会影响估计结果。
因此,两个节点显示相同或相近时间,并不代表它们观察到同一个绝对时刻。时间差越小,事件顺序越需要谨慎解释。
这不意味着物理时钟没有价值。它仍适合:
- 展示给人看的日志时间和审计时间;
- TTL、定时任务和时间窗口;
- 按日期分区和历史查询;
- 在已知最大偏差条件下提供外部一致性。
关键在于承认误差范围。Google Spanner 的 TrueTime 不返回一个假装精确的单点时间,而是返回包含真实时间的区间。系统等待不确定区间过去后,再对外确认事务顺序。它依赖 GPS、原子钟等时间基础设施,以及持续监控产生的误差边界。
普通系统通常没有这类硬件,但仍应同步时钟、监控偏差,并避免仅凭接近的墙上时间判断因果关系。
Lamport 时钟记录影响关系
Leslie Lamport 在 1978 年提出 happened-before(先发生)关系。它只关心一个事件是否可能影响另一个事件:
- 同一进程内,先执行的事件先于后执行的事件;
- 一条消息的发送先于对应的接收;
- 关系可以传递:若 A 先于 B,B 先于 C,则 A 先于 C。
没有任何影响路径的两个事件是并发事件。它们可能在现实时间上有先后,但系统缺少足够信息证明这种先后有业务意义。
Lamport 逻辑时钟用一个递增计数器表示这种关系:
本地事件或发送消息前:counter += 1
发送消息时:携带 counter
收到值 remote 后:counter = max(counter, remote) + 1
它保证:
A happened-before B => L(A) < L(B)
反方向并不成立。看到 L(A) < L(B),无法据此证明 A 导致了 B。两个完全无关的事件也会得到可以比较的数字。
如果再加入节点 ID 等稳定规则,Lamport 时间戳可以生成确定的全序,适合排序日志、消息或复制操作。这个全序包含人为打破并列的结果,不能自动变成真实因果关系。
向量时钟可以识别并发
向量时钟为每个参与者保存一个计数器。三个节点的时间可以写成:
[A, B, C]
节点 A 执行本地事件时增加 A 的位置;发送消息时携带整个向量;接收消息时逐项取本地和远端的最大值,再增加自己的位置。
比较两个向量时:
- 每一项都不大于对方,且至少一项更小,表示前者先发生;
- 两个方向都无法满足,表示事件并发;
- 完全相等,表示向量表达了相同的因果历史位置。
例如:
[2, 1, 0] < [3, 1, 2] // 前者先发生
[2, 1, 0] 与 [1, 2, 0] // 无法比较,事件并发
识别并发非常适合冲突检测。Amazon Dynamo 的经典论文使用向量时钟判断对象版本之间是否存在因果祖先关系,或已经分裂成需要合并的兄弟版本。购物车等业务随后决定如何合并内容。
向量时钟只负责发现冲突,不会替业务选择正确结果。两个副本并发修改用户名时,系统仍要决定保留哪一个;购物车可以合并商品集合,账户余额则需要完全不同的规则。
它的主要成本是元数据随参与者或版本来源增长。固定小集群容易处理,节点频繁加入退出、参与者数量很大时,需要版本向量、带点版本向量或其他压缩方式。原文将其概括为「节点越多,向量越大」,方向正确;实际成本取决于系统如何定义参与者身份和清理历史。
HLC 连接墙上时间与因果顺序
Hybrid Logical Clock(混合逻辑时钟,HLC)通常由两部分组成:接近物理时间的值,以及在时间冲突或倒退时递增的逻辑计数器。
它希望同时得到两种能力:
- 时间戳大致接近现实时间,便于查询、过期处理和排查问题;
- 若 A 先于 B,HLC 能保持 A 的时间戳小于 B;
- 元数据大小固定,无需携带与节点数同样长的向量。
HLC 仍无法像向量时钟那样,仅靠比较两个时间戳判断事件是否并发。它提供因果兼容的顺序,同时保留接近物理时间的表示。
CockroachDB 是常见应用案例。每个节点使用 HLC 给事务和消息赋时间戳,并配置允许的最大时钟偏差。读取遇到可能来自「未来」的值时,会通过不确定区间和事务重试保护一致性。
这里有一个重要边界:HLC 只是 CockroachDB 事务协议的一部分。事务正确性还依赖 MVCC、Raft 复制、锁、时间戳缓存和重试规则。单独给应用事件加上 HLC,不会自动获得数据库级一致性。
因果一致性保护有依赖的事件
因果一致性要求所有观察者按照因果顺序看到有关联的事件。没有因果关系的并发事件可以在不同节点上呈现不同顺序。
评论和回复是直观例子:
发布评论 A
用户看到 A 后发布回复 B
A happened-before B
系统不应先展示回复 B,再展示它所引用的评论 A。与此同时,两个互不相关的评论可以按不同顺序到达不同区域,无需为它们建立昂贵的全局顺序。
这个保证也常见于:
- 用户修改头像后立即发布动态,动态应引用新头像;
- 创建文档后分享文档,接收者不应先看到分享记录再遇到文档不存在;
- 配置更新触发任务,任务执行记录不应早于它依赖的配置版本;
- 服务调用链中,下游日志应保留上游请求的因果标识。
因果一致性比线性一致性允许更多并发,跨区域协调成本通常更低。它也需要系统传播依赖信息,并对会话、对象或操作定义清楚的因果范围。
四类时钟如何选择
它们回答的问题不同,不能按「先进程度」排成一条替换链。
| 机制 | 主要回答 | 能识别并发 | 接近现实时间 | 元数据规模 | 常见用途 |
|---|---|---|---|---|---|
| 物理时钟 | 大约在何时发生 | 否 | 是 | 固定 | 日志、TTL、时间查询 |
| Lamport 时钟 | 是否满足必要的因果顺序 | 否 | 否 | 固定 | 确定排序、复制操作 |
| 向量时钟 | 谁先发生,或是否并发 | 是 | 否 | 随参与者增长 | 版本冲突、因果追踪 |
| HLC | 接近现实时间且尊重因果 | 否 | 是 | 固定 | MVCC 时间戳、事务排序 |
如果目标是发现两个离线副本是否并发修改,向量时钟更直接。若系统需要时间范围查询,又希望避免时钟倒退破坏因果顺序,HLC 更合适。只需要稳定排序消息时,Lamport 时钟可能已经足够。
工程中容易踩的五个坑
用时间戳当唯一 ID
同一时刻可能生成多个事件,时钟还可能回拨。唯一 ID 应额外包含随机量、节点信息或序列,不能只依赖毫秒时间戳。
用较大的 Lamport 值推断因果
Lamport 时钟只保证有因果关系时数值递增。较大的值可能来自并发节点,判断因果需要消息路径、向量或显式依赖。
发现冲突后默认最后写入胜出
Last Write Wins 依赖时间戳选择一个版本,可能静默丢失并发更新。只有业务明确接受这种损失时才应使用。
只同步时钟,不监控偏差
NTP 服务正常运行也无法保证偏差永远在目标范围内。应监控 offset、同步状态和节点重启后的行为,并让超限节点停止承担依赖严格时间的工作。
日志只有墙上时间
跨服务排查时,建议同时记录 trace ID、请求 ID、父事件 ID 或逻辑时间。墙上时间帮助定位时间窗口,因果标识帮助还原真实调用链。
一份设计检查表
设计事件顺序或冲突处理前,可以先回答:
- 业务需要现实时间、确定排序,还是因果关系?
- 两个并发事件必须检测出来,还是允许任意稳定顺序?
- 时钟最大偏差如何配置、监控和处理超限?
- 节点身份是否稳定,向量元数据会增长到什么规模?
- 冲突由基础设施检测后,业务采用什么合并规则?
- 因果关系需要覆盖一次会话、一个对象,还是跨服务调用链?
- 日志能否同时提供墙上时间和可追踪的因果标识?
分布式系统缺少统一可信的瞬时时间,但不代表事件顺序无法讨论。物理时钟描述大致时间,Lamport 时钟保存必要顺序,向量时钟识别并发,HLC 在现实时间与因果顺序之间取得实用平衡。先明确业务真正关心的问题,再选择最小够用的时钟和一致性保证,通常能减少协调成本与隐藏冲突。
Aide Hub 会继续分享 AI 助手、开发工具和软件工程实践。
参考
- Harshit Khosla:Distributed Systems #2 (Time, Order, and Causality)
- Leslie Lamport:Time, Clocks and the Ordering of Events in a Distributed System
- Friedemann Mattern:Virtual Time and Global States of Distributed Systems
- Amazon:Dynamo: Amazon’s Highly Available Key-value Store
- Sandeep S. Kulkarni 等:Logical Physical Clocks
- Google Research:Spanner, TrueTime and the CAP Theorem
- Cockroach Labs:Hybrid Logical Clock Timestamps