前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >记一次 Redisson 线上问题 → 你怎么能释放别人的锁

记一次 Redisson 线上问题 → 你怎么能释放别人的锁

作者头像
青石路
发布2024-07-23 08:08:07
610
发布2024-07-23 08:08:07
举报
文章被收录于专栏:开发技术

线上问题

生产环境突然告警,告警信息:

attempt to unlock lock, not locked by current thread by node id: b9df1975-5595-42eb-beae-bdc5d67bce49 thread-id: 52

查看日志,找到对应的堆栈信息

代码语言:javascript
复制
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 idthread-id 是什么

关于 thread-id,我相信大家都理解,就是抛异常的线程的 id,没问题吧?那 node id 呢?

我用八股文引导下你们

问:redisson 用的 redis 的什么数据类型来实现锁的 答:hash 问:那 hash 中的 keyfieldvalue 的值分别是什么 答:key 的值是锁名,field 的值是 线程idvalue 的值是重入次数 问:如果多个服务同时去获取一把锁,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

testLock
testLock

这代码,我相信大家都能看懂,但我还是说明下

  1. 构造锁
  2. 尝试获取锁,等待时间1s,持锁3s
  3. 如果获取到锁,则进行业务处理,没获取到锁,则打印 锁获取失败
  4. finally 保证异常和非异常情况下,锁都能释放

是不是很正常,但真的没 bug

我们调整下代码

testLock_异常
testLock_异常

运行 multiThreadLock,异常就来了

异常信息
异常信息

从打印信息,我们应该能分析出问题出在哪

  1. 线程52获取到锁,执行业务中
  2. 线程53尝试获取锁,但锁被线程52持有
  3. 线程53 1s内获取锁失败
  4. 线程53 来到 finally,判断锁是否被持有,发现是被持有的,释放锁
  5. redisson 释放锁的时候,发现锁的持有线程并非当前线程,抛出异常

线程53,你怎么回事,怎么能释放别人的锁?可不能怪线程53,代码可是我们写的,看看提交记录,非得把这个二臂揪出来!!!

算了算了,还是别揪了,我们继续看如何修复

问题修复

既然找到问题了,修复问题就很简单了,方式有以下几种

提高等待时长

将获取锁的等待时长提高,但这种方式只能减少异常,并不是完全修复异常;因为会有多个线程同时竞争锁,等待时长设置成多少都不合适,除非设置成不超时,但是设置成不超时,可能会导致等待的线程太多,造成线程不够用的情况。不推荐该方式

自动释放

去掉 finally,相当于把产生异常的源头给干掉了,那肯定就不会有异常了嘛,这不就是我们常提到的

解决不了问题,那就把提出问题的人解决掉

不主动释放锁,让锁自动到期释放,因为我们设置了锁持有时长是 3s,3s 后就自动到期释放了。但在实际业务中,我们往往会把锁持有时长设置的比较大(远大于业务执行的平均时长),保证业务不会并发执行,如果业务执行完了不主动释放锁,就会导致很长时间内锁被无效占用,后面的线程获取锁也只能白白等待。不推荐该方式

记录获取状态

直接看代码,你们就懂了

记录获取状态_1
记录获取状态_1

如果业务执行时间超过 3s,会怎么样,我们把睡眠时间改成 5s,执行下 testLock,你会发现同样的异常又出现了!!!

记录获取状态_异常
记录获取状态_异常

我们来分析下,锁持有时长是 3s,而业务执行时长是 5s,也就说业务还没执行完,锁已到期,redis 自动释放了,业务执行完之后我们再去释放锁,锁都没了,怎么释放?所以 redisson 抛出异常了;所以释放锁的时候,还需要加一个条件

if (acquired && lock.isLocked())

acquired 表示当前线程是否获取到锁了,而 lock.isLocked() 表示是否有线程持有锁,如果都为 true,那就说明是当前线程持有锁,释放就没问题了。可以用,但不推荐,因为有更优雅的处理方式

判断持有者

这种写法更优雅

判断持有者
判断持有者

就直接判断锁是不是当前线程持有,是就可以释放;就不用去管锁是别的线程持有,还是到期自动释放了。推荐该方式

总结

  1. 示例代码地址:redisson-spring-boot-demo
  2. 加锁的目的就是为了保证业务单线程执行,所以锁的持有时长一定要设置大一点,不然极端情况下,业务还在执行中,锁却到期了,就违背了加锁的初衷
  3. 锁一定要主动释放、一定要主动释放、一定要主动释放,与业务无关
  4. 释放锁的时候,要判断是否是当前线程持有,都不是你的锁,你凭什么释放
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-07-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 线上问题
  • 基础回顾
  • 释放别人的锁
  • 问题修复
    • 提高等待时长
      • 自动释放
        • 记录获取状态
          • 判断持有者
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档