跳到主要内容
约 5 分钟2 张卡

分布式锁

分布式锁协调多个进程对共享资源的并发操作。本文以 Redis 6、7 为基线,讲解所有者、租约与旧请求拒绝。

1. 面试核心​

先记住这几条,再读正文
  • 原子获取。 SET key token NX PX ttl 原子完成锁占用与租约设置。
  • 所有者校验。 释放锁时,Lua 原子比较所有者 token 后删除。
  • 过期任务隔离。 租约到期后旧任务仍能运行,资源端需用 fencing token 拒绝旧编号。卡 · 原子加锁与过期
  • 安全续租。 续租要原子校验 token,失去所有权后停止延期。卡 · 原子续租
  • 复制与业务边界。 异步复制会留下锁丢失窗口,业务幂等另行约束重复副作用。

2. 原子加锁与过期​

单 Redis 节点可用一条命令取得锁。

SET resource:42 <random-token> NX PX 10000

NX 只在键不存在时写入。PX 设置 10000 毫秒租约。token 应标识本次获取,可用足够随机的唯一值。

SETNX 与 EXPIRE 分开执行时,中间存在崩溃窗口。只完成 SETNX 就退出,会留下没有期限的锁。

收到 OK 后才能按本次租约执行。请求超时时,客户端尚未确认是否取得锁。应按 token 核对或等待租约结束,避免盲目进入临界区。

3. 锁释放与所有者校验​

A 的锁过期后,B 可以取得同名锁。A 恢复后直接 DEL,会误删 B 的锁。

比较与删除需在 Redis 内原子执行。

if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
return 0

返回 1 表示删除了本次锁,0 表示未匹配当前 token。若 GET 与 DEL 分开执行,检查后仍有到期与重建窗口。

4. 租约时间​

租约需覆盖业务执行、网络往返和调度延迟。客户端应从剩余有效期中扣除获取锁已花费的时间。期限越长,崩溃后其他请求等待越久。

续租可延长正常运行的任务。进程冻结、长 GC 或网络隔离仍能使租约失效。任务恢复时要重新检查本次租约状态。

即使检测到失锁,已发出的外部请求也能继续执行。因此,外部资源还需拒绝旧持有者的操作。

5. 原子续租​

续租前需确认键仍属于本次获取。

if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('pexpire', KEYS[1], ARGV[2])
end
return 0

返回 1 表示本次键已延期,0 表示所有权已失效。直接 PEXPIRE 会把其他持有者的新锁延期。

续租超时也属于结果未知。客户端需按剩余租约预算停止发起新操作。已在途请求由资源端版本校验和业务幂等处理。

6. 锁过期与 fencing token​

A 取得 10 秒锁后暂停 15 秒。B 在锁到期后取得新锁,并开始写共享资源。A 恢复后也继续写,此时两个任务同时运行。

fencing token 为每次成功获取分配递增编号。资源端保存已接受的最大编号。请求编号小于该值时拒绝写入,较大时更新水位。校验与业务修改必须原子完成。

同一租约的后续请求可携带相同编号。业务应允许这个编号继续操作,并用请求 ID 去重。若每次只接受严格更大的编号,同一持有者只能写一次。

编号生成器需跨故障保持单调性。异步复制 Redis 上的 INCR 会面临计数回退。外部资源无法校验编号时,锁协议缺少拒绝旧写入的保证。

7. 主从故障转移​

异步复制的故障转移可丢失刚写入的锁。复制与恢复机制见 主从复制。

Redlock 在多个独立节点获取多数锁。获取总耗时还必须小于租约有效期,并扣除时钟漂移余量。它依赖租约与故障模型,仍需处理暂停后的旧执行者。

资金或库存的权威状态可优先使用数据库条件更新。需要协调外部资源时,可使用一致性协调服务并校验 fencing。

8. 业务幂等​

请求超时后,客户端无法仅凭响应判断操作是否完成。重试会再次触发临界区,因此需要独立的幂等机制。

  • 稳定请求 ID 标识同一次业务操作。
  • 唯一约束或状态条件限制重复修改。
  • 执行记录支持事后核对结果。
  • 重试设置次数和总时间上限。

锁未取得时,应明确返回、等待或排队策略。队列长度与等待时间也需设置上限。

9. 多资源加锁​

多个资源使用统一的获取顺序。例如按规范化资源 ID 升序逐个加锁。中途失败时,按各自 token 释放已取得的锁。下一次尝试使用有界随机退避。

不同顺序会形成循环等待,租约只能提供最终释放。跨资源原子更新仍需事务或明确的协调协议。

10. 常见问题​

10.1 所有者 token 的唯一性​

线程 ID 会在不同进程中重复。同一线程后续获取的锁也需与旧任务区分。因此,每次获取都应生成独立且难碰撞的 token。

10.2 watchdog 与租约​

11 · 面试题卡片​

11.1 Redis 分布式锁需要满足哪些条件,为什么还需要 fencing token​

出现公司:字节跳动、易点云

考察重点

  • SET NX PX、唯一 token 和 compare-and-delete。
  • 租约过期、进程暂停与异步复制故障转移。
  • fencing token、业务幂等与权威资源校验。

相关内容:原子加锁与过期至多资源加锁。

参考回答

锁需原子获取、唯一所有者标识和有限租约。

SET key token NX PX ttl 完成获取与过期设置。释放时用 Lua 原子比较 token 后删除。

租约到期后,旧任务仍能恢复运行。资源端需原子拒绝小于当前水位的 fencing token。同一租约的请求可用相同编号,再按请求 ID 去重。编号生成器还需跨故障保持单调性。

11.2 Redis 锁怎样安全续租,续租失败后应如何处理?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:所有者校验、剩余租约和在途请求。

相关内容:原子续租。

参考回答

用 Lua 原子比较 token,匹配后才执行 PEXPIRE。

返回 0 表示已经失去所有权。请求超时时,应按剩余租约停止发起新操作。旧任务和在途请求还需资源端 fencing 校验。

12. 参考资料​