Redis 分布式锁:加锁、续期与安全释放
Redis 分布式锁:加锁、续期与安全释放
进程内的 synchronized 只能协调同一个 JVM 中的线程。服务部署成多个实例后,实例 A 和实例 B 各自持有一把本地锁,彼此看不见。Redis 分布式锁可以让多个进程围绕同一个 Redis 键协调,但它有清晰的能力边界。
1. 最基本的加锁条件
客户端尝试以原子方式创建一个不存在的键,并同时设置过期时间。概念上相当于:
1 | |
NX 表示仅当键不存在时设置,PX 设置毫秒级租约。值要使用每次加锁唯一的 Token,用来识别锁的所有者。若命令成功,当前调用者获得锁;若键已存在,则本次未获得锁。
过期时间能避免进程崩溃后永久占锁,但它不是正确性的全部。业务执行超过租约后,锁可能过期并被其他客户端获得,原持有者仍可能继续执行。
2. 为什么不能直接 DEL
假设客户端 A 的锁过期,客户端 B 随后拿到同一个锁键。若 A 恢复后直接执行 DEL lock:order:123,它会删掉 B 的锁。释放锁必须同时检查 Token 是否仍属于自己,并且比较与删除应作为一个原子操作。
Redis Lua 脚本的思路如下:
1 | |
调用方将锁键和自己的随机 Token 传入脚本。不要拆成“先 GET,再 DEL”两条独立命令,否则两步之间仍有竞态窗口。
3. 锁租约和长任务
租约应结合任务时长、故障恢复和最大并发设计。固定租约过短可能使任务尚未结束锁就失效;过长则延缓故障后的恢复。Redisson 等客户端可以提供续期机制,但续期线程、进程暂停、网络分区和 Redis 故障仍需要纳入设计与监控。
4. 需要更强保护时使用 fencing token
当旧持有者可能在锁过期后继续写入共享资源时,仅检查锁状态不一定能阻止旧操作。Fencing token 是单调递增的令牌,资源端拒绝小于已接受令牌的写入。它需要资源本身支持校验;单靠普通 Redis 锁键并不能自动提供这一保护。
5. Redis 锁不等于业务唯一性
重要业务通常还应由数据库约束、条件更新或幂等键兜底。例如“一人一单”可用唯一索引防止重复记录,库存可用带条件的原子更新避免减成负数。锁用于减少竞争或串行化某段逻辑,数据约束才是持久化层的最终防线之一。
6. 何时不必自己造锁
如果应用已有成熟客户端,应先了解其可重入、等待、续期、取消和解锁语义,不要照着几条命令拼出一个看似完整的实现。对于需要严格一致性的关键流程,应评估数据库锁、队列单线程分区或其他协调服务。
参考:Redis 分布式锁模式。





