← 中文资源

简体中文版本

分布式锁

分布式锁是带失败窗口的租约:TTL 保证持有者崩溃后能继续推进,而围栏令牌防止过期持有者继续写入。

阅读英文原文

锁是一份带失败窗口的租约

持有者用一个较短的 TTL 和唯一 owner token 获取键。其他竞争者在键过期或被释放前无法获取。持有者崩溃时,TTL 让系统继续推进。但如果持有者暂停——GC、虚拟机冻结、网络延迟——它可能在 TTL 之后恢复,仍以为自己持有锁,而另一个进程此时也已持有。

const token = crypto.randomUUID();
const acquired = await redis.set('lock:account:42', token, 'PX', 30_000, 'NX');
if (!acquired) return { acquired: false };

// Release only if the token still matches.
await redis.eval(
  "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end",
  1,
  'lock:account:42',
  token
);

比较并删除的释放逻辑能避免删掉其他持有者的锁,但不能阻止过期持有者继续写入受保护资源。这需要围栏机制。

围栏才是安全机制

每次获取锁都分配一个单调递增的令牌,并把它包含在每次写入中。资源拒绝低于已见最高值的令牌。过期持有者可能仍以为自己有锁,但它的写入会失败。数据库序列、共识存储或对象版本都能提供令牌;随机 UUID 无法排序。

续租与活性

长任务需要看门狗在过期前续租,并且在任务取消或进程不健康时停止续租。没有续租的锁会在合法长任务期间过期,让第二个持有者进入;TTL 过长的锁则会在崩溃后阻塞进度。根据任务时长和续租节奏选择 TTL,并测试暂停场景。

何时使用

当重复执行有害,而受保护资源自身无法强制互斥时,使用分布式锁做协调。优点是跨进程互斥;代价是租约时序、时钟假设和围栏需求。与数据库唯一约束或乐观版本检查相比,锁更复杂、更容易出错;资源级防护可用时应优先采用。