跳到主要内容

分布式锁的成立条件与边界

分布式锁用于协调多个进程对共享资源的并发操作。一个可用的锁协议至少要区分锁的所有者、限制持有时间,并确保旧持有者不能误删新锁;涉及关键外部资源时,还要处理租约过期后的旧请求。

1. 单节点锁需要原子获取与自动过期

Redis 上常用一条原子命令获取锁:

SET resource:42 <random-token> NX PX 10000
  • NX 保证 key 不存在时才写入。
  • PX 设置租约时间,持有者崩溃后锁可以自动释放。
  • 随机 token 标识本次持有者,不能只保存固定进程名。

先执行 SETNX 再单独设置过期时间存在崩溃窗口:第一条成功、第二条尚未执行时进程退出,锁可能永不过期。

2. 释放时必须比较所有者 token

客户端 A 的锁已经过期,客户端 B 随后取得同名锁。如果 A 恢复后直接执行 DEL,就会删掉 B 的锁。

释放操作应在 Redis 内原子完成“值仍等于自己的 token 才删除”,例如使用 Lua 脚本:

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

读取 token 和删除分成两条命令仍有竞态。检查通过后、删除执行前,锁可能已经到期并被他人取得。

3. 租约时间必须覆盖临界区

锁的 TTL 要覆盖排队、业务执行和释放所需时间,并留出网络与调度抖动。TTL 太短会让业务尚未完成时锁已经过期;TTL 太长则在持有者崩溃后阻塞其他请求更久。

自动续租可以延长正常长任务,但必须明确:

  • 只有当前 token 持有者可以续租。
  • 业务完成或失去锁后停止续租。
  • 续租失败时临界区如何尽快停止。
  • 进程暂停超过 TTL 后,不能假设自己仍持有锁。

GC 暂停、主机冻结和网络隔离都可能让持有者在租约到期后继续运行。

4. 锁过期后可能同时存在两个执行者

客户端 A 取得 10 秒锁后暂停 15 秒。锁到期后 B 取得锁并开始写文件;A 随后恢复,也继续写同一个文件。Redis 中任意时刻可能只有一个锁 key,但外部系统已经同时收到两个持有者的操作。

对不能接受旧执行者写入的资源,需要 fencing token:每次成功取锁获得单调递增编号,资源端只接受比已处理编号更大的请求。旧持有者恢复后携带较小编号,存储服务会拒绝它。

如果外部资源无法校验 fencing token,分布式锁只能降低并发概率,不能单独证明业务操作互斥。

5. 主从故障转移会影响锁安全

Redis 复制默认异步。客户端在主节点创建锁后,锁记录可能尚未复制,主节点便发生故障;落后的副本被提升后,另一个客户端可以再次取得同名锁。

Redis 官方的 Redlock 算法尝试在多个相互独立的节点上取得多数锁,并要求整个过程在租约有效期内完成。它的安全性仍依赖时钟漂移、网络延迟和临界区时长等假设。使用前要明确故障模型,而不是把“多数节点成功”理解为任何情况下都绝对互斥。

对资金、库存权威状态等高风险操作,可以优先使用数据库唯一约束、条件更新、具备共识的一致性协调系统,或由权威资源校验 fencing token。

6. 业务操作仍需幂等与状态校验

锁不会自动处理客户端超时、进程崩溃和结果丢失。调用方没有收到响应时,无法确认临界区是否已经完成,再次执行可能产生重复副作用。

业务应同时具备:

  • 稳定请求 ID 或幂等键。
  • 数据库唯一约束或条件状态转换。
  • 失败后可核对的执行记录。
  • 有限重试和总 deadline。
  • 锁未取得时明确返回、等待或排队策略。

锁是并发协调的一层,不是业务正确性的全部来源。

7. 多资源锁要固定获取顺序

一次操作同时锁多个资源时,所有调用方应使用相同顺序,例如按规范化资源 ID 排序后依次获取。获取中途失败,要释放自己已经持有的锁,并使用随机退避重试。

即使单个锁实现正确,不同顺序仍会形成分布式死锁。更复杂的跨资源原子性需求,通常说明应重新设计数据所有权或让操作进入一个串行执行单元。

8. 常见问题

8.1 锁中保存线程 ID 可以吗

仅在单进程内可能足够,跨进程时会重复,而且同一线程后续任务可能误用旧标识。每次获取应生成全局难以碰撞的唯一 token,并把 token 与本次锁生命周期绑定。

8.2 有 watchdog 自动续期后是否不需要 TTL

仍然需要。进程崩溃或与 Redis 失联时,续租会停止,TTL 是最终释放机制。watchdog 也不能防止长暂停超过 TTL 后旧任务继续操作外部资源。

9. 面试题

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

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

考察重点

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

相关内容:第 1 节“单节点锁需要原子获取与自动过期”至第 7 节“多资源锁要固定获取顺序”。

参考回答

单 Redis 节点上可以用 SET key token NX PX ttl 原子获取锁,token 必须标识本次持有者;释放时在 Redis 内原子比较 token 后删除,避免旧持有者删掉新锁。TTL 用于持有者崩溃后的自动释放,续租只能延长正常任务,不能消除进程暂停超过租约的情况。

租约到期后,旧持有者仍可能恢复并操作外部资源;主从异步复制故障转移也可能让锁记录丢失。因此关键资源应校验单调递增的 fencing token,或者使用数据库约束和一致性协调系统。业务还需要幂等键、状态条件和有限重试,分布式锁本身不能保证所有副作用只发生一次。