尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
看门狗每 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%。思考题看门狗解决了「业务慢导致锁提前过期」但引入了「业务永久卡死时锁也永久续期」。如果你的临界区里有调用第三方支付这种不可控耗时的操作你会怎么设计锁的持有策略和超时
RELATED

相关推荐

Java 静态变量

Java 静态变量

静态变量的定义在 Java 中,静态变量(也称为类变量)是指被声明为 static 关键字的变量,它们属于类而不是实例。静态变量在类加载时被初始化,且所有实例共享同一份静态变量副本。静态变量可以通过类名直接访问&#xff0…

📅 2026/10/14 2:38:46
大数据招聘信息数据分析:基于Python网络爬虫的IT招聘就业岗位数据分析可视化推荐系统

大数据招聘信息数据分析:基于Python网络爬虫的IT招聘就业岗位数据分析可视化推荐系统

文章目录基于Python网络爬虫的IT招聘就业岗位数据分析可视化推荐系统项目概述全套资料获取招聘岗位数据爬虫分析系统展示用户注册登录系统首页IT招聘数据开发岗-javaIT招聘数据开发岗-PythonIT招聘数据开发岗-AndroidIT招聘数据开发岗-其它招聘岗位数据分析算法方面运维方面测试…

📅 2026/10/31 19:59:37
java对象所占内存大小输出

java对象所占内存大小输出

如何计算java对象所占内存大小&#xff0c;可以使用下述三种方法。使用jdk8自带API使用下面语句打印对象所占内存大小&#xff1a;需要保证jdk版本是jdk8。System.out.println(ObjectSizeCalculator.getObjectSize(3L));借助org.apache.lucene工具类引入maven坐标<dependenc…

📅 2026/10/18 0:55:47
MORE NEWS

更多资讯

📰

淄博考电工证到哪里考千万别踩坑,新手必看的报名避坑指南

淄博考电工证到哪里考千万别踩坑,新手必看的报名避坑指南 你是不是也像我一样,第一次听说要考电工证,心里一万个问号?不知道去哪报名怕被中介坑,怕花了钱考了证又没用,怕被坑了还不能上岗?别急,今天就来给你说清楚, 淄博考电工证到哪里考…

📰

宁河登高架设资格证有效期多久 良心建议给学历不高怕报不上名的人

宁河登高架设资格证有效期多久 良心建议给学历不高怕报不上名的人 学历不高怕报不上名?宁河登高架设资格证其实没你想象那么难!很多人觉得考资格证必须得是高学历,其实不然, 只要符合年龄、健康等基本条件,任何人都可以报考…

📰

青海考电工证培训机构怎么选?郑州报考避坑指南帮你少走弯路

青海考电工证培训机构怎么选?郑州报考避坑指南帮你少走弯路 怕考不过白交培训费?青海考电工证培训机构选错了,钱花了,证没拿到,白忙一场。不少人在报考电工证时,都被培训机构“套路”了,花了大价钱,结果培训质量差、考试通过率低。今天就从…

📰

巴中电工证培训时长怎么安排才能拿到电子证书

巴中电工证培训时长怎么安排才能拿到电子证书 证书过期了不知道怎么复审延期,是不是你正在经历的烦恼?别急,这期内容就帮你搞清楚【巴中电工证培训时长】、考试真题、备考技巧,以及电子证书的相关流程。如果你的证书快要到期了,现在开始准备也不晚。…

📰

市区怎么考取电工证官方回应实操考试心里没底怕挂科

市区怎么考取电工证官方回应实操考试心里没底怕挂科 实操考试心里没底怕挂科?你不是一个人在战斗。最近在郑州房建工程一线,不少电工都反映考试紧张、怕挂科,甚至有人因为没通过实操考试耽误了工期和项目。其实,很多人对【市区怎么考取电工证】的流程、要…

📰

杭州低压电工证试题怎么准备?郑州报考避坑指南帮你少走弯路

杭州低压电工证试题怎么准备?郑州报考避坑指南帮你少走弯路 实操考试心里没底怕挂科,这几乎是每个准备考低压电工证的人心里的共同恐惧。尤其是像杭州、郑州这类一线或新一线城市,电工证不仅是上岗的硬门槛,还直接关系到未来的职业发展和收入水平。如果你…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬