首页/数据/Redis 进阶速查/分布式锁:SET NX PX 与安全释放
数据结构与选型42 条

分布式锁:SET NX PX 与安全释放

加锁用 SET key value NX PX ttl 一步完成(原子),value 用唯一标识(如 UUID 加线程号);释放时必须校验持有者,避免误删他人锁。

Redis 进阶速查数据更新于 2026-10-13

分布式锁与 SET NX PX

基于 Redis 的分布式锁本质是一条键:加锁成功意味着键不存在并被自己写入,解锁意味着删除这条键。正确的最小实现是一条命令完成的 SET key value NX PX ttl。其中 NX 保证只有键不存在时才写入,PX 同时设置毫秒级过期时间,两者在服务端原子完成,避免了先 SETNX 再 EXPIRE 之间抢锁者崩溃导致死锁的历史问题。value 必须写入调用方唯一标识,后续释放时才能判断锁是不是自己的。

基本信息

版本前提SET 支持 NX/PX 组合自 Redis 2.6.12 起,7.0 起可用 GETDEL
加锁命令SET lock:order:1001 <uuid> NX PX 30000
释放方式Lua 内比较 value 相等后 DEL,两步 GET 加 DEL 不安全
续期业务未执行完时延长 TTL,通常由看门狗线程按剩余时间续约
返回值加锁成功返回 OK,失败返回 nil

典型使用场景

  • 同一订单同一时刻只能被一个节点处理,用业务主键做 key 天然互斥
  • 定时任务在多实例部署下只允许一台执行,抢到锁的实例负责本轮计算
  • 缓存重建互斥:热点键失效时只放一个请求回源,其余等待或复用旧值
  • 对上游有严格配额限制的操作做串行化,避免并发超限被封禁
  • 跨服务的短临界区保护,锁粒度按业务主键而非全局唯一

加锁与释放

加锁与释放
# 加锁:一条命令原子完成 设置值 + 唯一标识 + 过期时间
redis-cli SET lock:order:1001 d9f2-8c01-4a77 NX PX 30000
# 返回 OK 表示拿到锁;返回 nil 表示已被他人持有,直接放弃或退避重试
redis-cli PTTL lock:order:1001          # 观察剩余租期

# 释放:EVAL 保证「比较 + 删除」原子
redis-cli EVAL "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end" 1 lock:order:1001 d9f2-8c01-4a77

续期与重试

续期与重试
-- 续期:只有仍持有锁时才延长 TTL,避免给别人的锁续命
if redis.call('GET', KEYS[1]) == ARGV[1] then
  return redis.call('PEXPIRE', KEYS[1], tonumber(ARGV[2]))
else
  return 0
end

常见写法对错

写法问题正确做法
SETNX 之后再 EXPIRE两步之间进程崩溃则锁永久不过期用 SET key value NX PX ttl 一条命令
DEL 直接删锁可能删掉别人刚抢到的同名锁Lua 比较 value 后再 DEL
只设置一次性长 TTL业务慢于 TTL 时锁提前失效看门狗按剩余时间续期
锁 value 用固定值无法区分持有者,释放判断失效写入 UUID 或机器加线程唯一标识
TTL 与业务耗时的关系是设计重点:TTL 短于业务耗时会出现两个持有者同时进入临界区,长于业务耗时则崩溃后要等很久才自动解锁。工程上通常把 TTL 设为预估耗时的数倍,再用续期把长任务的锁维持住。
单实例锁在主从切换窗口内可能失效:主节点写入锁后尚未异步复制就宕机,从节点提升为主后锁并不存在,另一个调用方可以拿到同一把锁。对一致性极端敏感的场景可评估多节点多数派加锁,或改用有共识语义的系统。
加锁失败不是错误,而是竞争结果,必须有明确的后续策略:非关键路径直接快速失败返回「处理中」;关键路径按固定间隔退避重试并设总超时;高并发场景在应用内先做一次短等待,减少对 Redis 的无效轮询。
锁只能降低并发概率,不能替代幂等。任何持锁者都可能因为 GC、网络分区或超时而失去锁,临界区内被保护的操作必须设计成可重复执行。

易错点

  • 先 GET 判断再加 DEL 的两步写法在并发下会删掉别人刚拿到的锁,客户端库若没有内建安全释放,必须自己用 Lua 或单命令实现
  • 忘记给锁设置唯一 value 或用固定字符串,释放时就无法区分持有者,一旦发生误删后续请求会同时在临界区执行
  • 业务耗时超过锁 TTL 却未续期,临界区被两个进程同时进入,这类问题往往在压测或流量高峰才暴露
  • 把锁当队列或状态机用:锁键过期即消失,无法承载业务状态,等待方需要自己实现退避与超时放弃逻辑

数据来源与参考

  • Redis 官方文档:SET 命令(NX/PX 选项)
  • Redis 官方文档:分布式锁模式
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌