看门狗每 10 秒续一次期,业务却跑了 40 秒:Redisson 锁续期源码里藏着的 2 个默认陷阱

发布时间:2026/10/21 16:48:32

看门狗每 10 秒续一次期,业务却跑了 40 秒:Redisson 锁续期源码里藏着的 2 个默认陷阱 title: 看门狗每 10 秒续一次期业务却跑了 40 秒Redisson 锁续期源码里藏着的 2 个默认陷阱date: 2026-08-22category: 分布式锁tags: [Java, 后端, Redis, Redisson, 并发编程]我们订单服务用 Redisson 做分布式锁锁一段「扣减库存 生成订单」的逻辑。某天凌晨告警库存出现了超卖同一件商品被下了两单。查日志发现两个线程都拿到了同一把锁几乎同时进入临界区。翻到 Redisson 源码才明白问题出在「看门狗」和我们写锁的方式上——这个坑我们踩了两次才彻底搞清。事故现场两把锁同时生效那段代码最早是这样写的RLock lock redissonClient.getLock(stock: productId); // 显式指定 30 秒过期 lock.lock(30, TimeUnit.SECONDS); try { // 扣库存 建订单偶发需要 35~50 秒 orderService.create(productId); } finally { lock.unlock(); }看起来没毛病30 秒过期业务跑完释放。但orderService.create在大促时偶发要 40 秒超过了 30 秒。按我们的理解Redisson 有「看门狗」会自动续期锁不会提前失效——可事实是业务跑到 30 秒时锁真的过期了另一个线程拿到了锁。看门狗源码它只在「不指定 leaseTime」时才工作翻 Redisson 的RedissonLock#lock()和scheduleExpirationRenewal关键在这里// RedissonLock 内部续期调度简化 private void scheduleExpirationRenewal(long threadId) { // 只有 leaseTime -1即未显式指定才进入看门狗逻辑 if (this.expirationRenewalMap.containsKey(threadId)) { return; } Timeout task commandExecutor.getConnectionManager() .newTimeout(new TimerTask() { Override public void run(Timeout timeout) { // 每 internalLockLeaseTime / 3 续一次期 RFutureBoolean future renewExpirationAsync(threadId); future.onComplete((res, e) - { if (res) { // 递归续期默认 30s 租约每 10s 续一次 scheduleExpirationRenewal(threadId); } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); }逐行讲三个关键点看门狗的触发前提是leaseTime -1。一旦你像我上面那样写了lock(30, SECONDS)Redisson 认为「你已经明确知道要锁多久」就不再启动看门狗锁就老老实实 30 秒后过期。默认租约internalLockLeaseTime是 30 秒看门狗每30 / 3 10秒续一次期靠 Lua 脚本把过期时间重新刷成 30 秒。续期也是用 Lua 保证原子pexpire只对「还持有锁」的 key 生效如果锁已被别人拿走续期静默失败不会把别人的锁续上。所以正确用法是不指定 leaseTime让它走看门狗RLock lock redissonClient.getLock(stock: productId); // 不传 leaseTime默认走看门狗每 10s 自动续期 lock.lock(); try { orderService.create(productId); // 跑多久都行锁不会提前掉 } finally { // 释放时 Redisson 会取消看门狗定时任务 lock.unlock(); }第二个陷阱unlock 不在 finally 里看门狗线程永远不取消我们的第二版又踩了另一个坑有同学把unlock()写在业务逻辑后面没包在finally里。业务抛异常时unlock没执行看门狗还在每 10 秒续期这把锁成了永久锁直到 Redis key 手动删掉。Redisson 在unlock()内部会调用cancelExpirationRenewal取消续期任务// RedissonLock#unlock 内部简化 public void unlock() { // 释放锁的 Luadel key 并发布解锁消息 RFutureBoolean future unlockAsync(); future.onComplete((opStatus, e) - { // 关键取消看门狗的定时续期 cancelExpirationRenewal(); }); }所以unlock必须在finally块里确保无论业务成功还是抛异常都能执行续期任务才能被正确取消。我们后来统一封装了一层模板方法public T T withLock(String key, SupplierT action) { RLock lock redissonClient.getLock(key); lock.lock(); // 走看门狗 try { return action.get(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }isHeldByCurrentThread()这个判断很重要如果锁已经因为超时显式 leaseTime 场景被 Redis 清掉再unlock会抛IllegalMonitorState异常。加了判断超时不释放的锁就不会在 finally 里再炸一次。第三个容易被忽略的点tryLock 的等待与续期很多人用tryLock(waitTime, leaseTime, unit)来「抢不到就放弃」这里面的坑是只要传了 leaseTime看门狗同样不工作。// 想要「抢不到等 3 秒、抢到后自动续期」要分两步写 boolean locked lock.tryLock(3, TimeUnit.SECONDS); // 第一参是等待时间不传 leaseTime if (locked) { try { orderService.create(productId); // 看门狗生效不怕业务慢 } finally { lock.unlock(); } }注意tryLock(3, SECONDS)这种单参写法和tryLock(3, 30, SECONDS)完全不同前者只设等待时间、leaseTime 仍是 -1看门狗生效后者设了等待时间也设了 leaseTime30看门狗失效。少写一个参数的差别就是锁会不会提前掉。看门狗续期失败的边界看门狗不是万能的。它依赖「续期 Lua 能成功执行」如果那 10 秒窗口里 Redis 主节点宕机、或网络抖动导致续期失败锁还是会过期。我们遇到过一次 Redis 网络分区续期连续两次失败锁在 30 秒后过期业务又出现了短暂的并发重入。这也是为什么 Redisson 官方反复强调分布式锁在极端网络异常下做不到绝对互斥它只能把「误释放」的概率压到很低不能完全消除。如果你要的是「绝对不能重复执行」得在业务层再补一道幂等比如用请求唯一 ID 去重表而不是把所有信任都押在锁上。锁的粒度比锁的实现更影响吞吐踩完超卖坑之后我们才意识到锁的 key 设计比「用哪种锁」更影响性能。最初我们getLock(createOrder)锁整个下单入口结果不同用户的下单请求被串行化吞吐量掉了一半。改成getLock(stock: productId)只锁单个商品后不同商品之间完全不互斥。进一步我们把一个大库存拆成多个子库存行比如按仓库维度热点商品的锁竞争又降了一截。锁的命名空间要精确到「真正会冲突的资源」而不是笼统的业务方法名——这一条对 Redisson、Redis 手写锁、ZooKeeper 锁都成立。锁太粗等于自己给自己做限流锁太细又可能出现「该互斥的没互斥」这个度要靠压测数据来定不是拍脑袋。我们曾把一个粗粒度锁锁整个商家拆成细粒度锁单个商品下单接口 P99 从 320ms 降到 140ms这个数据比任何理论都直观——锁粒度优化带来的吞吐提升经常比换锁实现更明显。几个方案对比写法看门狗是否生效风险lock()无参生效每 10s 续期业务永久卡住会一直续需 finally 释放lock(30, SECONDS)不生效业务超 30s 锁提前掉并发重入tryLock(3, 30, SECONDS)不生效同上且抢不到直接返回锁不在 finally 释放续期任务不取消成为永久锁直到手动清我的取舍很明确绝大多数场景用无参lock()finally释放把续期交给看门狗只有「业务执行时间有硬上限、超时就该放弃」的场景才显式指定 leaseTime并且接受它不会自动续期、需要自己控制业务耗时。把锁当「长期持有」用的同学务必要配好finally和幂等兜底别把看门狗当免死金牌。复盘数字那次超卖发生在凌晨 02:14持续约 6 分钟涉及 3 个商品共 11 笔重复下单资损约 2400 元。修复上线后我们用压测复现单热点商品 500 并发、业务耗时随机 20~50 秒连续跑 30 分钟零重复下单同时监控看门狗续期日志每把锁稳定每 10 秒打印一次renew expiration确认续期链路正常。后续我们又把锁粒度从「整个下单方法」细化到「单商品库存行」热点商品的锁竞争下降了约 70%。思考题看门狗解决了「业务慢导致锁提前过期」但引入了「业务永久卡死时锁也永久续期」。如果你的临界区里有调用第三方支付这种不可控耗时的操作你会怎么设计锁的持有策略和超时
延伸阅读

更多相关文章

2026/10/20 23:06:40

linear mixed models for ecology in R

背景:数据点不独立,有嵌套(伪重复问题)例如:同一物种被测量了多个数据;同一地点采集了多个数据常用模型:mod lmer (data , formula y ~ Fixed_Factor (Random_intercept Random_Slope | Ran…

2026/10/21 21:05:36

VC6.0中使用CImage实现PNG透明贴图与Alpha混合的完整指南

简介:面向仍在使用VC6.0开发环境的C工程师,压缩包以完整示例工程演示加载PNG图片并实现透明效果的方法,讲解基于GDI图形扩展库处理图像透明度通道、借助颜色矩阵调整透明度的核心思路,同时覆盖窗口及控件透明化的实现方式。压缩包…

2026/10/21 21:05:36

微信小程序BLE开发踩坑记02|系统蓝牙可见却搜不到?

背景 上一篇刚把"安卓搜不到设备"那事摆平,我还挺得意,以为扫描这块算是摸清了。结果一接血压计,又懵了:同一台手机、同一套扫描代码,血氧仪一搜就出来,血压计扫半天页面干干净净。 更邪门的是&a…

2026/10/21 21:05:36

C# WinForm 嵌入外部 EXE 窗口:SetParent 实战与避坑指南

简介:这份资源面向具备一定C#基础的WinForm开发者,聚焦于将外部EXE程序嵌入窗体这一常见但实现路径多样的技术需求。包内共25个文件,以cs源码、exe可执行程序、resx资源文件、csproj工程文件及sln解决方案为主,另含pdb调试符号与m…

2026/10/21 21:05:36

服务器磁盘选型与Linux磁盘管理实战:从RAID配置到故障排查

服务器选型最容易翻车的环节,往往不是 CPU 和内存,而是磁盘。CPU 不够可以堆核,内存不够可以加条,但磁盘的接口类型、RAID 级别、文件系统布局一旦定错,后期改造成本非常高。尤其在高并发业务、数据库场景、日志采集场…

2026/10/21 21:05:36

青蛙游戏开发实战:从手感调优到本地化发布的独立游戏完整链路

如果你刷到过这条帖子,大概会觉得它只是一个轻松的求助:一位美国独立开发者做好了青蛙游戏,请大家帮忙取一个中文名。事情看起来很小,但站在技术开发者的角度看,这个举动释放的信号比“取名字”本身重要得多——它意味…

2026/10/21 21:00:34

HarmonyOS 7+ArkTS:精准碰一碰回执幂等判定与确认门禁【鸿蒙心迹】

碰一碰分享完成后,应用界面收到一条“接收成功”的通知,看起来应该立刻把素材加入列表。这里有一个容易忽略的细节:通知抵达和用户确认接收,不一定是同一件事。设备靠近、页面恢复、通信重试可能让业务适配层交出内容相同的回执。…

2026/10/21 4:53:08

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/21 0:00:35

交通流仿真实战:IDM跟驰与MOBIL换道模型参数调优与避坑指南

简介:这份资源是面向交通工程学习者与Python开发者的微观交通流仿真项目,核心实现智能驾驶模型(IDM)下的车辆跟驰与换道行为,可用于理解速度、密度与流量之间的动态关系,适合具备Python基础、希望深入交通模…

2026/10/21 0:00:35

揭穿智能体表面顺从:用追踪代码重建任务执行确定性

1. 表面顺从不是智能体的“礼貌”,而是任务执行链路上的隐性断点最近在调试一个跨平台长周期任务调度系统时,我反复遇到一种令人困惑的现象:智能体在用户指令发出后,几乎秒级响应“已收到”“正在处理”“请稍候”,UI上…

2026/10/21 0:00:35

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑