跳转到内容

Lamport 逻辑时钟 — 分布式系统里先后顺序怎么说清楚

待复核

日常类比:两个人分头排队买票,只能靠短信沟通。你能确定的是”我先发短信,你后收到短信”;至于两个城市里各自按按钮的瞬间谁更早,如果没人互相通知,系统其实不知道。

Lamport 这篇论文做的事,就是把分布式系统里的”先发生”讲清楚:一个事件能影响另一个事件,才算它在前面。

这条关系后来叫 happens-before。它不是普通钟表时间,而是一种因果顺序:同一个进程里前面的操作在后面操作之前;消息发送在消息接收之前;如果 A 在 B 前、B 在 C 前,那么 A 在 C 前。

论文再给每个进程一个只会往前走的数字计数器,也就是逻辑时钟。它不能告诉你真实几点几分,却能保证:如果 A 确实因果上早于 B,那么 A 的时间戳一定小于 B。

不理解 Lamport 逻辑时钟,下面这些事都没法解释:

  • 为什么分布式系统没有一个天然的”全局现在”;每台机器只看到自己的局部故事。
  • 为什么消息顺序比机器时间更可靠;物理时钟会漂移,消息因果关系不会凭空倒退。
  • 为什么复制状态机、互斥锁、日志复制都要先定义”所有人按什么顺序执行命令”。
  • 为什么后来的向量时钟、分布式快照、Paxos、因果一致性都绕不开这篇 1978 年论文。
  1. 先定义能影响谁:同一进程的前后、消息的发送接收、传递闭包,三条规则合成 happens-before。类比:查快递路径,只承认有扫描记录的转运链路。

  2. 逻辑时钟是计数器,不是手表:每个进程做事前把本地数字加一,发消息时带上数字,收消息时把本地数字跳到更大。类比:每个小组都有页码本,收到别人第 7 页的信,就把自己翻到第 8 页以后再写。

  3. 全序是工程选择,不是真理:逻辑时钟加进程编号可以把所有事件排成一条队,但并发事件的先后只是为了实现方便。类比:两个同时来的外卖单,系统必须排队处理,可这个顺序不代表现实里谁更”早”。

案例 1:单个进程里计数器怎么走

Section titled “案例 1:单个进程里计数器怎么走”
clock = 0
def local_event(name):
global clock
clock += 1
print(name, clock)
local_event("read config")
local_event("send request")

逐部分解释

  • clock += 1 对应论文里的 IR1:同一进程里后一个事件必须拿到更大的数字。
  • 输出的 12 只表达本进程内部顺序,不保证它们对应真实秒表时间。
  • 这已经足够让本进程的事件不会在日志里倒着出现。
def receive(local_clock, message_time):
return max(local_clock, message_time) + 1
p1_send_time = 5
p2_clock = 2
p2_clock = receive(p2_clock, p1_send_time)
print(p2_clock) # 6

逐部分解释

  • 消息带着 p1_send_time = 5,表示发送事件发生在逻辑时间 5。
  • 接收方不能还停在 2;否则会出现”收到 5 号信,却说自己在 3 号时间处理”的倒挂。
  • max(...)+1 对应 IR2:接收事件必须晚于发送事件。
requests = [
(4, "P2", "use printer"),
(4, "P1", "use printer"),
(7, "P3", "use printer"),
]
for item in sorted(requests):
print(item)

逐部分解释

  • 先按逻辑时间排;时间相同再按进程名打破平局。
  • 论文用这招做分布式互斥:所有进程只要看到同一批请求,就会排出同一条队。
  • (4, "P1")(4, "P2") 谁更早,可能只是人为规定,不是因果事实。
  1. 把逻辑时钟当真实时间:它只保证因果早晚,不保证 6 点事件真的比 5 点事件晚一秒。

  2. 以为小时间戳一定 happened-before 大时间戳:论文只保证 A -> BC(A) < C(B),反过来不成立,因为并发事件也会被排出大小。

  3. 忽略外部世界的顺序:用户先打电话再点按钮,这个”电话”如果不进系统日志,算法看不见它。

  4. 以为全序解决所有故障:论文里的互斥算法需要所有进程参与;一个进程停住时,别人无法判断它是慢还是坏。

适用

  • 分布式日志、事件追踪、消息系统里判断”这个事件是否可能影响那个事件”。
  • 实现复制状态机、分布式互斥、请求排序等需要大家同意顺序的机制。
  • 给初学者建立分布式系统第一层直觉:先别急着看锁,先问事件之间有没有因果链。

不适用

  • 需要知道真实墙钟时间的业务,比如”订单必须在 12:00 前提交”。
  • 需要区分所有并发关系的场景;Lamport 时钟会把并发事件也硬排成大小。
  • 需要容错和多数派提交的场景;那要继续学 Paxos、Raft、拜占庭问题等协议。
  • 1976 年:论文初稿收到,Lamport 已经在思考”分布式系统里的时间到底是什么”。
  • 1978 年:论文发表在 Communications of the ACM,happens-before 和逻辑时钟正式进入分布式系统词汇表。
  • 1985 年:Chandy 和 Lamport 用类似的因果视角提出分布式快照,解决”不停机拍全局状态”的问题。
  • 1988-1989 年:Fidge、Mattern 等工作把逻辑时钟推广成向量时钟,用多个数字保留更多并发信息。
  • 1990 年代以后:复制状态机和 Paxos 把”大家按同一顺序执行命令”变成可靠分布式服务的基本套路。
  1. 分布式系统先缺的不是锁,而是共同时间感:机器分开以后,“谁先谁后”必须重新定义。

  2. happens-before 是因果关系,不是日历时间:能沿着进程顺序和消息链走到,才叫在前。

  3. 逻辑时钟把因果关系压成数字:数字好排序、好写日志、好做协议,但会丢掉并发细节。

  4. 物理时钟只在必要时加入:当用户外部感知顺序会影响正确性时,才需要讨论时钟同步误差。

  • chandy-lamport-1985 —— happens-before 思想直接服务于分布式快照。
  • byzantine-generals-1982 —— 同样来自 Lamport,关注不可靠参与者下的一致决策。
  • paxos-1998 —— 把状态机顺序推进到容错共识协议。
  • paxos-simple-2001 —— 更适合初学者读的 Paxos 解释版。
  • cops-2011 —— 因果一致性系统需要追踪操作之间的依赖关系。
  • spanner-2012 —— 用 TrueTime 把物理时钟误差也纳入分布式事务设计。
  • consistency-models-2023 —— 学完事件顺序后,可以继续理解不同一致性承诺。
  • lamport-bakery —— Lamport Bakery — 用取号排队解决并发互斥
  • raft-2014 —— Raft 2014 — 把共识拆成能实现的三件事
  • skeen-3pc-1981 —— Skeen 1981 三阶段提交 — 给 2PC 的阻塞缺陷打补丁