分布式锁
分布式锁协调多个进程对共享资源的并发操作。本文以 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 与租约
watchdog 通过续租延长锁期限。进程崩溃或失联后,续租会停止,TTL 负责最终释放。暂停超过 TTL 的旧任务仍需由资源端 fencing 拦截。
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 校验。