记一次 Redisson 线上问题 → 你怎么能释放别人的锁
开心一刻
今天,我的又一个好哥们脱单了,只剩下我自己单身了
我向一个我喜欢的女生吐苦水
我:我这辈子是找不到女朋友了
她:怎么可能,你很优秀的,会有很多女孩子愿意当你女朋友的
我内心窃喜,问道:那你愿意当我女朋友吗
她:我都在开导你了,你不要恩将仇报!
线上问题
生产环境突然告警,告警信息:
attempt to unlock lock, not locked by current thread by node id: b9df1975-5595-42eb-beae-bdc5d67bce49 thread-id: 52
查看日志,找到对应的堆栈信息
Exception in thread "thread0" java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: b9df1975-5595-42eb-beae-bdc5d67bce49 thread-id: 52
at org.redisson.RedissonLock.lambda$unlockAsync$4(RedissonLock.java:616)
at org.redisson.misc.RedissonPromise.lambda$onComplete$0(RedissonPromise.java:187)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:578)
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:552)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:491)
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:184)
at org.redisson.misc.RedissonPromise.onComplete(RedissonPromise.java:181)
at org.redisson.RedissonLock.unlockAsync(RedissonLock.java:607)
at org.redisson.RedissonLock.unlock(RedissonLock.java:492)
at com.qsl.ResissonTest.testLock(ResissonTest.java:41)
at java.lang.Thread.run(Thread.java:748)
翻译过来就是
企图去释放锁,不被当前线程(node id: b9df1975-5595-42eb-beae-bdc5d67bce49 thread-id: 52)锁住
也就是:当前线程企图去释放别的线程的锁
怎么能释放别人的锁?
基础回顾
在排查问题之前,我们先弄清楚
node id: b9df1975-5595-42eb-beae-bdc5d67bce49 thread-id: 52
node id
和 thread-id
是什么
关于 thread-id
,我相信大家都理解,就是抛异常的线程的 id,没问题吧?那 node id
呢?
我用八股文引导下你们
问:
redisson
用的redis
的什么数据类型来实现锁的答:
hash
问:那
hash
中的key
、field
、value
的值分别是什么答:
key
的值是锁名,field
的值是线程id
,value
的值是重入次数问:如果多个服务同时去获取一把锁,
field
的值是不是有可能相同,比如服务A获取锁的线程的thread-id
是 52,服务B获取锁的线程的的thread-id
也是 52此时你是不是有点慌了,但依旧嘴硬的回答:有可能相同
问:那没问题吗,A服务的线程(
thread-id=52
)拿到锁后,正在执行业务处理,B服务的线程(thread-id=52
)也能拿到锁,这不是锁了个寂寞?答:呃...嗯...
很显然漏了个细节,那就是 field
,其值不是 线程id
,而是 node id:thread-id
,例如:b9df1975-5595-42eb-beae-bdc5d67bce49:52
,而这个 node id
就是 redisson
的 实例id
,用以区分分布式下的 redisson
实例
Redisson 分布式锁实现之源码篇 → 为什么推荐用 Redisson 客户端 有很详细的介绍,值得你们看看
释放别人的锁
talk is sheap show me the code
这代码,我相信大家都能看懂,但我还是说明下
- 构造锁
- 尝试获取锁,等待时间1s,持锁3s
- 如果获取到锁,则进行业务处理,没获取到锁,则打印
锁获取失败
finally
保证异常和非异常情况下,锁都能释放
是不是很正常,但真的没 bug
吗
我们调整下代码
运行 multiThreadLock
,异常就来了
从打印信息,我们应该能分析出问题出在哪
- 线程52获取到锁,执行业务中
- 线程53尝试获取锁,但锁被线程52持有
- 线程53 1s内获取锁失败
- 线程53 来到
finally
,判断锁是否被持有,发现是被持有的,释放锁redisson
释放锁的时候,发现锁的持有线程并非当前线程,抛出异常
线程53,你怎么回事,怎么能释放别人的锁?可不能怪线程53,代码可是我们写的,看看提交记录,非得把这个二臂揪出来!!!
算了算了,还是别揪了,我们继续看如何修复
问题修复
既然找到问题了,修复问题就很简单了,方式有以下几种
提高等待时长
将获取锁的等待时长提高,但这种方式只能减少异常,并不是完全修复异常;因为会有多个线程同时竞争锁,等待时长设置成多少都不合适,除非设置成不超时,但是设置成不超时,可能会导致等待的线程太多,造成线程不够用的情况。不推荐该方式
自动释放
去掉 finally
,相当于把产生异常的源头给干掉了,那肯定就不会有异常了嘛,这不就是我们常提到的
解决不了问题,那就把提出问题的人解决掉
不主动释放锁,让锁自动到期释放,因为我们设置了锁持有时长是 3s,3s 后就自动到期释放了。但在实际业务中,我们往往会把锁持有时长设置的比较大(远大于业务执行的平均时长),保证业务不会并发执行,如果业务执行完了不主动释放锁,就会导致很长时间内锁被无效占用,后面的线程获取锁也只能白白等待。不推荐该方式
记录获取状态
直接看代码,你们就懂了
如果业务执行时间超过 3s,会怎么样,我们把睡眠时间改成 5s,执行下 testLock
,你会发现同样的异常又出现了!!!
我们来分析下,锁持有时长是 3s,而业务执行时长是 5s,也就说业务还没执行完,锁已到期,redis
自动释放了,业务执行完之后我们再去释放锁,锁都没了,怎么释放?所以 redisson
抛出异常了;所以释放锁的时候,还需要加一个条件
if (acquired && lock.isLocked())
acquired
表示当前线程是否获取到锁了,而 lock.isLocked()
表示是否有线程持有锁,如果都为 true
,那就说明是当前线程持有锁,释放就没问题了。可以用,但不推荐,因为有更优雅的处理方式
判断持有者
这种写法更优雅
就直接判断锁是不是当前线程持有,是就可以释放;就不用去管锁是别的线程持有,还是到期自动释放了。推荐该方式
总结
- 示例代码地址:redisson-spring-boot-demo
- 加锁的目的就是为了保证业务单线程执行,所以锁的持有时长一定要设置大一点,不然极端情况下,业务还在执行中,锁却到期了,就违背了加锁的初衷
- 锁一定要主动释放、一定要主动释放、一定要主动释放,与业务无关
- 释放锁的时候,要判断是否是当前线程持有,都不是你的锁,你凭什么释放
记一次 Redisson 线上问题 → 你怎么能释放别人的锁的更多相关文章
- 记一次 android 线上 oom 问题
背景 公司的主打产品是一款跨平台的 App,我的部门负责为它提供底层的 sdk 用于数据传输,我负责的是 Adnroid 端的 sdk 开发. sdk 并不直接加载在 App 主进程,而是隔离在一个单 ...
- 记一次排查线上MySQL死锁过程,不能只会curd,还要知道加锁原理
昨晚我正在床上睡得着着的,突然来了一条短信. 啥,线上MySQL死锁了,我赶紧登录线上系统,查看业务日志. 能清楚看到是这条insert语句发生了死锁. MySQL如果检测到两个事务发生了死锁,会回滚 ...
- MySQL数据库如何线上修改表结构
一.MDL元数据锁 在修改表结构之前,先来看下可能存在的问题. 1.什么是MDL锁 MySQL有一个把锁,叫做MDL元数据锁,当对表修改的时候,会自动给表加上这把锁,也就是不需要自己显式使用. 当对表 ...
- 记一次线上bug排查-quartz线程调度相关
记一次线上bug排查,与各位共同探讨. 概述:使用quartz做的定时任务,正式生产环境有个任务延迟了1小时之久才触发.在这一小时里各种排查找不出问题,直到延迟时间结束了,该任务才珊珊触发.原因主要就 ...
- NOI Day2线上同步赛崩盘记
Preface 蒟蒻愉快的NOI线上赛Day2之行,不过因为太菜就凉了 这次由于策略&&网络的问题,最后两题都没有交,结果就靠T1稳住拿了75分就回家了. 我真是太菜了. 屠龙勇士 首 ...
- 解Bug之路-记一次线上请求偶尔变慢的排查
解Bug之路-记一次线上请求偶尔变慢的排查 前言 最近解决了个比较棘手的问题,由于排查过程挺有意思,于是就以此为素材写出了本篇文章. Bug现场 这是一个偶发的性能问题.在每天几百万比交易请求中,平均 ...
- 记一次线上Curator使用过程JVM栈溢出解决
为了同学们看起来一目了,特按如下思路进行讲解. 1.出现的场景 2.分析及解决的过程 3.总结 最近公司要使用zookeeper做配置管理(后面简称ZK),然后自己就提前用虚拟机进行 ...
- 记一次线上coredump事故
1.事故背景 上周三凌晨,我负责的某个模块在多台机器上连续发生coredump,幸好发生在业务低峰期,而且该模块提供的功能也不是核心流程功能,所以对线上业务影响比较小.发生coredump后,运维收到 ...
- 记一次令人窒息的线上fullgc调优
今天第二篇采坑了... ... 现场因为处理太急促没有保留,而且是一旁协助,没有收集到所有信息实在是有些遗憾...只能靠记忆回想一些细节 情况是一台服务器一启动就开始full gc,短短1分钟可以有几 ...
- 记Booking.com iOS开发岗位线上笔试
今晚参加了Booking的iOS职位线上笔试,结束后方能简单归纳一下. 关于测试内容: Booking采用了HackerRank作为测试平台,测试总时长为75分钟,总计4道题. 测试之前我很紧张,因为 ...
随机推荐
- ASP.NET Core SignalR 概述
什么是 SignalR? ASP.NET Core SignalR 是一个开放源代码库,可用于简化向应用添加实时 Web 功能. 实时 Web 功能使服务器端代码能够将内容推送到客户端. 适合 Sig ...
- Pyomo基础学习笔记:建模组成要素的编写方法
1.Pyomo 简介 pyomo文档[数学建模]优化模型建模语言 Pyomo 入门教程 - 知乎 (zhihu.com) Pyomo 是基于 Python 的开源软件包,主要功能是建立数学规划模型,包 ...
- zabbix笔记_001
zabbix介绍 zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案. zabbix能监视各种网络参数,保证服务器系统的安全运营:并提供灵活的通知机制以让系统管 ...
- c++从零实现reactor高并发服务器!!!
环境准备 linux虚拟机 安装升级c/c++编译器 gcc/g++ 选项 源代码文件1 源代码文件2 ... 源代码文件n -o指定输出的文件名(不能和源文件同名 默认是a.out) -g调试 -O ...
- NET工控,上位机,Modbus485网口/串口通讯(鸣志步进电机,鸣志伺服电机,松下伺服电机,华庆军继电器模块)
先上两个通用Modbus帮助类,下面这个是多线程不安全版,在多线程多电机同一端口通信下,可能造成步进电机丢步或者输出口无响应等,还有个多线程安全版,只是基于这个不安全版加上了LOCK,THIS usi ...
- itest work(爱测试) 开源接口测试&敏捷测试管理平台 9.5.0 发布,接口测试及脑图用例升级
(一)itest work 简介 itest work (爱测试) 一站式工作站让测试变得简单.敏捷,"好用.好看,好敏捷" ,是itest wrok 追求的目标.itest w ...
- 机器学习算法(一):1. numpy从零实现线性回归
系列文章目录 机器学习算法(一):1. numpy从零实现线性回归 机器学习算法(一):2. 线性回归之多项式回归(特征选取) @ 目录 系列文章目录 前言 一.理论介绍 二.代码实现 1.导入库 2 ...
- kettle从入门到精通 第五十八课 ETL之kettle HTTP post使用教程
1.今天群里有位朋友问我有没有关于调用http接口的kettle 示例,我下意识的去翻我的公众号推文,愣是没找到.果断开始撸. 2.本次演示流程通过调用接口[网易云音乐随机歌曲],然后解析返回的数据, ...
- vm ware 虚拟WIN10 时,chrome ,cent browser 显示异常,花屏
类似: 解决方法: 在VM WARE 显卡设置中关闭"加速3D图形". -
- 微信刷脸SDK获取sub_openid
当调用SDK中 获取用户信息(getWxpayfaceUserInfo) /人脸支付凭证(getWxpayfaceCode) 方法获取 sub_openid 时,除了SDK自身要传入sub_appid ...