bigywater.tech
分布式事务 · 27

时钟:逻辑时钟与混合逻辑时钟

分布式系统里“谁先谁后”靠什么判断?物理时钟有偏差,会让接收早于发送。Lamport 时钟、向量时钟、混合逻辑时钟各保证什么、各付出什么?拿它们做最后写入者胜会怎样?这篇用 3 个真实进程互发消息、注入时钟偏差,把它们的行为数出来。

140 / 276
时钟偏差 [0, +50, -80] ms 时,接收事件物理时间戳比发送事件还小的消息数(偏差为 0 时是 0)
87.3%
Lamport 时钟给真正并发的事件对排出了先后的比例(向量时钟判定 100% 正确)
44.7% vs 0%
LWW 用物理时钟时间戳与用 HLC 时间戳,因果上在后的写入被压掉的比例

物理时钟的问题

实测 · 本机执行输出
  时钟偏差               | 收到的消息数 | 接收时间戳 < 发送时间戳的消息数 | 最大倒退量 
  -------------------+--------+-------------------+-------
  三个节点时钟完全一致(偏差 0)   | 276    | 0                 | 0 ms  
  偏差 [0, 50, -80] ms | 276    | 140               | 130 ms
  偏差为 0 时(本机同一个时钟)没有一条消息倒退;偏差 [0, +50, -80] 时,从“快”的节点发给“慢”的节点的消息,在接收方看来是“来自未来”——这里注入的偏差只是模拟机器之间的时钟不同步

节点之间的时钟偏差让因果上在后的事件时间戳更小。

Lamport 时钟

实测 · 本机执行输出
  共 713 个事件;Lamport 条件 “发送的 L < 接收的 L” 被违反的消息数:0
  抽样 19964 对:有因果关系的 19727 对,真正并发的 237 对
  并发的 237 对里,Lamport 给出了先后(L 不相等)的有 207 对(87.3%):单看 Lamport 值分不清“有因果”还是“碰巧数字大小不同”
  有因果关系的 19727 对里,按物理时间戳反而是“后发生的更小或相等”的有 196 对(1.0%)
  向量时钟对全部 19964 对的判定(a 先于 b / b 先于 a / 并发)和真值一致的有 19964 对(100.0%)

保证因果在前则数字更小,但数字大小不能反推因果,并发事件也被排出了先后。

向量时钟

实测 · 本机执行输出
  节点数 N | Lamport(1 个整数) | HLC(l, c) | 向量时钟(N 个整数)
  ------+----------------+-----------+------------
  3     | 5 字节           | 19 字节     | 21 字节      
  10    | 5 字节           | 19 字节     | 70 字节      
  50    | 5 字节           | 19 字节     | 350 字节     
  200   | 5 字节           | 19 字节     | 1400 字节    
  Lamport 和 HLC 的大小不随节点数增长,向量时钟线性增长;换来的是向量时钟能精确判定并发

能精确区分先于、后于、并发,代价是每条消息带 N 个计数器。

混合逻辑时钟

实测 · 本机执行输出
  “发送的 HLC < 接收的 HLC” 被违反的消息数 0;抽样的有因果关系的 9690 对里,HLC 顺序和因果相反的 0 对(同一批 9690 对里,物理时间戳顺序和因果相反或相等的有 80 对)
  l 与事件发生时本节点物理时钟的差(l - pt):最小 -1 ms,最大 130 ms(注入的偏差之差最大是 130 ms);c 的最大值 10,c > 0 的事件占 55.4%
  HLC 把“因果”交给逻辑部分、把“离真实时间不远”交给物理部分:时间戳还能当成大致的墙上时间用,偏差也有上界(取决于节点间时钟偏差)

l 追物理时间、c 补序号:因果不违反,时间戳离真实时间的差有上界,消息头不随节点数增长。

LWW 的时间戳

实测 · 本机执行输出
  LWW 用的时间戳   | 写入对数 | 因果上在后的 v2 被压掉的次数 | 比例   
  ------------+------+------------------+------
  物理时钟时间戳     | 1000 | 447              | 44.7%
  HLC 时间戳     | 1000 | 0                | 0.0% 
  Lamport 时间戳 | 1000 | 0                | 0.0% 
  用物理时钟做 LWW,两个节点时钟差 130 ms:相隔不到 130 ms 的因果写入,后面的会输给前面的(这里相隔 0 到 300 ms 均匀分布,理论上约 130/300 = 43%);HLC 和 Lamport 保证因果在后的时间戳更大

物理时钟做 LWW 会丢掉因果在后的写入;HLC 或 Lamport 不会。

分步演示

逻辑时钟与 HLC
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MySQL 26.7.0 与 PostgreSQL 16.15(Homebrew,每个实验自己 initdb、自己起实例,端口 9150 以上,只监听 127.0.0.1)/ Python 3.14.8 + PyMySQL 1.2.3 + psycopg 3.3.6 / Redis 8.10.2 / Kafka 4.3.1 / RabbitMQ 4.3.6 / etcd 3.7.2(按各篇注明用到哪些);不连任何外部服务,不碰本机已有的数据库实例;故障用 kill -9 进程、让连接断开或在代码里的指定位置抛异常来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇第一到第四节用 3 个独立的 Python 进程(UDP 互发消息,每进程 4 秒)和人为注入的物理时钟偏差([0, +50, -80] ms,是把 time.time() 加上偏移量,不是真的让系统时钟不同步);HLC 按 Kulkarni 等人的更新规则实现;“谁先于谁”的真值由消息收发图的可达性计算;第五节是用同样的规则对随机写入对做的计算,不是进程实测
    • 没有验证:真实 NTP / PTP 的偏差与漂移大小;时钟回拨、闰秒;Spanner TrueTime 的提交等待;HLC 在更多节点、更高消息速率下 c 的增长;向量时钟的精简(版本向量、dotted version vectors);本篇的节点数只有 3 个,并发事件对只有二百多对,比例的精度有限
    京ICP备2026067502号-1京公网安备11010502063487号