拔掉电源。按下紧急停止开关。当你站在单台机器旁时,这些直觉是有效的。但当你的 AI 系统跨越三个可用区、分布在 50 个节点上时,这些直觉就会失效。大多数工程团队都是通过惨痛教训才明白这一点的。他们更新中央数据库,将一个布尔值从 true 改为 false,然后以为系统就此停止了。事实并非如此。数据库看起来很干净,但服务仍在运行。

单一开关的错觉

想象一下,一个控制器在 epoch 12 记录了一次撤销。它将更改写入持久化存储,并松了一口气。与此同时,Worker B 仍在运行基于 epoch 11 的缓存授权。该工作节点从未收到通知。三十秒后,它开始执行模型推理任务、启动 GPU 集群或调用外部 API。审计日志显示访问已被撤销,但操作还是发生了。

这就是持久化与传播之间的鸿沟。数据库写入并不等同于系统状态。它只是某张表中的一行,而系统中的许多参与者在需要时并不会去轮询该表。如果你把紧急停止当成电灯开关,你会发现房间的某些角落永远不会陷入黑暗。

分布式系统的残酷现实

你必须为故障而设计。不是为了偶尔的故障,而是为了持续的、混乱的、独立的故障。工作节点在任务执行中途重启。队列消费者延迟数分钟。授权服务因为某个副本卡住而返回陈旧数据。消息重复、消息丢失、消息乱序到达。你的 NTP 守护进程发生漂移,突然间某个节点认为自己比其他节点慢了 10 秒。时钟存在误差,你无法信任墙上时间(wall time)来跨边界对事件进行排序。

如果你的紧急协议假设网络是可靠的、消息是按序交付的或时钟是同步的,那么你拥有的不是协议,而是一个愿望。工作节点、队列消费者和授权服务是独立发生故障的。即使基础设施表现得极具敌意,你的安全规则也必须依然有效。

五条真正有效的规则

安全源于在混乱中依然成立的不变量。以下是防止“撤销”变成空谈的规则。

任何操作的授权 epoch 都不能低于撤销 epoch。
这是你的核心护栏。每一次权限授予都携带一个 epoch 编号。每一次撤销都携带一个更新的编号。在任何工作节点采取行动之前,它都会对比这些编号。如果该节点的授权比它见过的最新撤销编号还要旧,该节点就会停止工作。Epoch 为你提供了一个不依赖于系统时钟的逻辑时钟。一旦持有 epoch 11 的工作节点得知 epoch 12 已撤销底层权限,它必须拒绝开始工作。

缓存的授权必须在设定的时间限制内过期。
权限绝不能永远存在于内存中。工作节点需要在限定的时间间隔后重新验证或放弃其权利。如果没有这一点,一个掉线的节点可能会在几天或几周后重新上线,并使用过时的授权执行操作。设置租约(lease),并严格执行。时间将成为你的自动清理员。

系统重启不能降低已保存的 epoch。
持久化至关重要。如果控制器崩溃并重启,它必须恢复其曾经发布过的最高 epoch。回滚到旧的 epoch 会使已被撤销的权限“复活”,仿佛紧急停止从未发生过一样。在广播 epoch 之前,请将其持久化存储。使用预写日志(write-ahead log)、确认过的 fsync 或复制共识组(replicated consensus group)。历史只能向前推进。

重复的撤销