资讯动态

Redis 分布式锁怎么加才安全?SETNX 手写、Redisson 看门狗与 Redlock 的 5 种实现

发布时间:2026/10/9 3:01:57 来源:尧图企业网站定制
我不会起名字322· 后端 / 算法 / 数据库 技术栈 力扣 Hot100 Go 项目 Redis MySQL文章目录Redis 分布式锁怎么加才安全SETNX 手写、Redisson 看门狗与 Redlock 的 5 种实现为什么 SETNX EXPIRE 两条命令一定会出事怎么保证只删自己的锁业务没跑完锁就过期了怎么办Redisson 的看门狗为什么有时不生效主从切换会把锁弄丢吗Redlock 到底能不能用锁的等待时间和超时时间怎么设怎么验证锁真的生效了5 种实现怎么选一张表看完上线检查清单一句话总结Redis 分布式锁怎么加才安全SETNX 手写、Redisson 看门狗与 Redlock 的 5 种实现生产上真正安全的加锁写法只有 3 种单实例用SET key value NX PX ms一条命令加锁、用 Lua 脚本比对 value 后释放主从架构用 Redisson 的看门狗自动续期跨机房才考虑 Redlock并且要接受它给不了强一致。其余写法SETNXEXPIRE两条命令、GET完再DEL、用完不删锁都会在某个并发窗口里出事。下面这套是我按「先给结论、再给能复制的代码、最后给选型表」整理的5 种实现的区别、Redisson 看门狗为什么有时不生效、主从切换丢锁的窗口有多大都能直接对照排查。为什么SETNXEXPIRE两条命令一定会出事因为这两条命令不是原子的SETNX成功之后、EXPIRE执行之前只要进程被杀、网络抖动或 GC 停顿这把锁就永远没有过期时间留在 Redis 里变成死锁后面所有请求全部阻塞。// ❌ 错误写法两条命令之间有一个能死锁的窗口Booleanokredis.setnx(lock:order:1001,1);if(ok){redis.expire(lock:order:1001,30);// 这一行没执行到 → 永久锁doBusiness();}正确写法只需要一条命令。Redis 从 2.6.12 开始支持SET的NX/PX参数加锁、设值、设过期一次性完成// ✅ 正确写法一条命令完成不存在则写入 30 秒过期StringtokenUUID.randomUUID():Thread.currentThread().getId();Stringrredis.set(lock:order:1001,token,SetParams.setParams().nx().px(30_000));if(!OK.equals(r)){// 没抢到锁直接失败或者短睡 50 ms 后重试最多重试 3 次}关键点有 3 个NX保证互斥、PX 30000保证锁一定会自动释放、value 存的是只有自己知道的 token这是下一节的重点。怎么保证只删自己的锁把 value 设成唯一标识UUID 线程 ID释放时用 Lua 脚本先比对再删除绝对不要直接用DEL也不要先GET判断再DEL。先看GETDEL为什么不行。假设 A 拿到锁、TTL 30 秒A 的业务卡了 31 秒第 30 秒锁自动过期B 拿到同一把锁第 31 秒A 回来执行GET读到的 value 已经是 B 的A 紧接着DEL—— 删掉的是B 的锁第 32 秒C 也能拿到锁此时 B 和 C 同时在跑同一段业务。GET和DEL之间的这个窗口没法靠代码顺序消除只能把比对 删除放进 Lua让 Redis 单线程原子执行-- release.lua值相等才删否则什么都不做ifredis.call(GET,KEYS[1])ARGV[1]thenreturnredis.call(DEL,KEYS[1])elsereturn0end// 一次 EVAL 完成比对 删除返回 1 表示真的删掉了自己的锁Longret(Long)redis.eval(RELEASE_LUA,Collections.singletonList(lock:order:1001),token);if(retnull||ret0L){log.warn(释放锁失败锁已过期或被别人持有key{},lock:order:1001);}第 3 类常见事故是「用完不删锁」靠 TTL 拖到过期业务明明 200 ms 就跑完了锁却占着 30 秒吞吐直接掉 150 倍。释放锁要放在finally里并且释放失败要打日志、上监控。业务没跑完锁就过期了怎么办给锁续期。Redisson 默认lockWatchdogTimeout 30000 ms30 秒后台任务每 10 秒也就是 1/3续一次只要 JVM 线程还持有锁、还没调用unlock()TTL 就会被一直续回 30 秒。// Redisson无参 lock() 会启动看门狗业务跑多久都不会因为超时被别人抢锁RLocklockredisson.getLock(lock:order:1001);lock.lock();// 等价于 tryLock(0, -1, TimeUnit.SECONDS)try{doBusiness();// 跑了 5 分钟也没事看门狗每 10 秒续一次}finally{lock.unlock();}续期的代价是多了一次后台定时任务和每 10 秒一次的 Redis 写。压测时能直接看到一个持有 5 分钟的锁PTTL lock:order:1001稳定在 30000 附近跳动而不是一路降到 0。有一种情况看门狗救不了你JVM 长时间 STW比如 8 GB 堆跑 Full GC 卡了 12 秒。续期任务本身也被挂起锁可能在停顿时过期而你的业务线程恢复后还以为自己持有锁。所以业务耗时要留余量堆和 GC 也要单独盯。Redisson 的看门狗为什么有时不生效因为你显式传了leaseTimetryLock(waitTime, leaseTime, unit)里只要它是正数Redisson 就不启动看门狗锁到点无条件释放。这是生产上最常见的一个坑因为代码看起来完全正常传 30 秒只是想让锁别占太久结果业务跑到第 31 秒锁就没了双持有悄悄发生。// ❌ 传了 30 秒 leaseTime看门狗不启动业务跑到第 31 秒锁就没了lock.tryLock(3,30,TimeUnit.SECONDS);// ✅ 想用看门狗leaseTime 传 -1lock.tryLock(3,-1,TimeUnit.SECONDS);// ✅ 或者干脆用无参 lock() / lockInterruptibly()lock.lock();写法是否启动看门狗锁的释放时机适合的场景lock.lock()✅ 是unlock()时释放无法预估业务耗时tryLock(wait, -1, unit)✅ 是unlock()时释放想设等待时间、不想设租期tryLock(wait, 30, unit)❌ 否30 秒到点自动释放业务耗时确定、且明显小于 30 秒tryLock(wait, 0, unit)❌ 否立刻视作已释放不要用等于没加锁版本差异要知道Redisson 的默认值和新旧 API 在不同版本里略有调整上面这张表按lockWatchdogTimeout30000 ms、leaseTime-1的通用行为写。上线前花 2 分钟看一眼你实际用的那版源码或文档比背结论靠谱。主从切换会把锁弄丢吗会。Redis 主从复制是异步的客户端在主节点写入锁主节点还没把这条命令同步给从节点就宕机从节点升主后根本不知道这把锁存在第二个客户端立刻就能拿到同名锁于是两个客户端同时进临界区。这个窗口的大小等于「主从复制的网络往返 故障检测时间」通常是几十毫秒到几秒。没有哪个配置项能把它消掉只能承认它在。部署形态会不会丢锁丢锁窗口工程建议单实例不会除非实例挂了无能用单实例就别上集群最简单也最安全主从 / 哨兵会复制延迟 故障切换几十 ms ~ 数秒临界区必须幂等或加 fencing tokenCluster会同上且槽迁移时也可能出问题同上多实例 Redlock概率极低但存在时钟漂移 网络延迟只有跨机房才值得引入真正稳的兜底不是把锁做得更复杂而是让临界区本身重复执行也不出错幂等或者用fencing token每次拿锁时取一个单调递增的版本号写下游时带上它下游只接受更大的版本号老持有者的写入会被直接拒绝。Redlock 到底能不能用能用但它解决的是「多数派同时认为锁有效」不是「绝对互斥」。算法要求 5 个互相独立的 Redis 实例客户端依次加锁只有在N/21 3个实例上加锁成功、且总耗时小于 TTL 时才算拿到锁。过程拆开是 4 步记下起始时间T1依次向 5 个实例发SET key value NX PX ttl每个实例都设很短的超时比如 50~200 ms失败就跳过统计成功数≥3且T1到现在的耗时小于 TTL 才算成功真正的有效时间 TTL - 加锁耗时 - 时钟漂移补偿如果这个值 ≤ 0说明锁已经过期要立刻向所有实例发解锁。争议点是真实存在的Kleppmann 在 2020 年那篇批评里指出Redlock 依赖「各实例时钟不会大幅漂移」这个假设而 GC 停顿、时钟跳变都会破坏它antirez 的回应是这些假设在工程上可接受。到 2026 年这个争论依然没有定论所以我的建议很保守单机房不要用 Redlock单实例SET NX PX或 Redisson 就够了它带来的复杂度远大于收益跨机房、且业务能接受极小概率的双持有可以用用 Redisson 的RedissonMultiLock老的RedissonRedLock已被标记过时绝对不能双持有扣款、库存、发券别把正确性压在分布式锁上用数据库唯一键 / 状态机 fencing token 来兜底。锁的等待时间和超时时间怎么设给一组可以直接抄的经验值waitTime设 50~200 msleaseTime要么-1交给看门狗、要么设成业务 P99 耗时的 2 倍以上重试用订阅而不是while自旋。自旋的代价很直观20 个线程在 100 ms 内反复重试会让 Redis 上的抢锁命令从 20 次涨到 200 次以上等于把 QPS 放大 10 倍锁没抢到反而先把 Redis 的 CPU 打满。参数建议值设错的后果waitTime等锁时间50~200 ms设太大 → 线程池被等锁的请求占满设 0 → 一次抢不到就报错leaseTime租期-1或 ≥ P99 耗时 × 2设太小 → 业务没跑完锁就没了出现双持有重试方式Redisson 订阅通知while自旋 → 抢锁流量放大 10 倍以上Redis CPU 打满unlock时机finally里且校验返回值漏掉 → 锁被 TTL 拖到过期吞吐掉 100 倍以上怎么验证锁真的生效了跑三个动作就够了并发压测看有没有双持有、看PTTL会不会被续期、统计加锁失败率与等待耗时的 P99。这三件事都不需要额外组件redis-cli加一段多线程脚本就能做。# 1) 观察续期持有锁期间反复看 TTL看门狗生效时它会被续回 30000redis-cli PTTL lock:order:1001# 期望稳定在 30000 附近而不是一路降到 0# 2) 观察互斥同一个 key 不应该出现两个持有者redis-cli--scan--patternlock:*|wc-l# 期望并发压测期间不超过并发线程数# 3) 观察失败率把加锁失败、等待耗时打到监控里# lock_acquire_fail_total / lock_wait_ms(P99) / lock_renew_total / lock_unlock_fail_total// 用 20 个线程抢同一把锁统计同时在临界区的最大值它必须恒等于 1AtomicIntegerinCriticalnewAtomicInteger();AtomicIntegermaxOverlapnewAtomicInteger();ExecutorServicepoolExecutors.newFixedThreadPool(20);for(inti0;i20;i){pool.submit(()-{RLocklockredisson.getLock(lock:verify);lock.lock();try{intnowinCritical.incrementAndGet();maxOverlap.accumulateAndGet(now,Math::max);// 期望恒为 1Thread.sleep(20);}catch(InterruptedExceptionignored){}finally{inCritical.decrementAndGet();lock.unlock();}});}5 种实现怎么选一张表看完实现加锁原子性自动续期主从切换丢锁实现成本建议SETNXEXPIRE❌ 否❌会低不要用有永久死锁风险SET NX PX Lua 释放✅ 是❌ 不续期会低单实例首选锁租期要大于业务耗时RedissonRLock看门狗✅ 是✅ 每 10 秒会低引一个依赖绝大多数后端的默认选择RedissonMultiLockRedlock✅ 是✅概率极低高要 5 个独立实例只在跨机房且能容忍极小概率双持有时用数据库唯一键 / 状态机✅靠事务不适用不会中金额、库存类业务的正解别用锁硬扛上线检查清单加锁是不是一条命令SET NX PX或 Redisson没有SETNXEXPIRE的组合value 是不是唯一 tokenUUID 线程 ID释放走 Lua 比对删除释放锁在finally里并且unlock()失败会打日志、进监控用 Redisson 时leaseTime是不是-1或没传否则看门狗不生效waitTime是不是设成了 50~200 ms并且没有while(true)自旋锁的 key 有没有业务前缀和粒度lock:order:{orderId}而不是一把全局大锁临界区里的操作是不是幂等或者带上了单调递增的 fencing token有没有用 20 线程压测验证过「同时在临界区 1」并且把结果截图存档监控上有没有lock_acquire_fail_total、lock_wait_msP99、lock_renew_total、lock_unlock_fail_total这 4 个指标故障演练做过没有主节点kill -9之后业务会不会双持有演练结论写进文档了吗一句话总结SET key value NX PX一条命令 Lua 比对删除是 90% 场景的正确答案Redisson 的看门狗只在leaseTime -1时才启动Redlock 解决的是多数派问题、不是正确性问题。真正让并发安全落地的是幂等和 fencing token而不是把锁写得更花哨。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑