开心一刻

今天,我的又一个好哥们脱单了,只剩下我自己单身了

我向一个我喜欢的女生吐苦水

我:我这辈子是找不到女朋友了

她:怎么可能,你很优秀的,会有很多女孩子愿意当你女朋友的

我内心窃喜,问道:那你愿意当我女朋友吗

她:我都在开导你了,你不要恩将仇报!

线上问题

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

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 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

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

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

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

我们调整下代码

运行 multiThreadLock,异常就来了

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

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

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

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

问题修复

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

提高等待时长

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

自动释放

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

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

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

记录获取状态

直接看代码,你们就懂了

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

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

if (acquired && lock.isLocked())

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

判断持有者

这种写法更优雅

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

总结

  1. 示例代码地址:redisson-spring-boot-demo
  2. 加锁的目的就是为了保证业务单线程执行,所以锁的持有时长一定要设置大一点,不然极端情况下,业务还在执行中,锁却到期了,就违背了加锁的初衷
  3. 锁一定要主动释放、一定要主动释放、一定要主动释放,与业务无关
  4. 释放锁的时候,要判断是否是当前线程持有,都不是你的锁,你凭什么释放

记一次 Redisson 线上问题 → 你怎么能释放别人的锁的更多相关文章

  1. 记一次 android 线上 oom 问题

    背景 公司的主打产品是一款跨平台的 App,我的部门负责为它提供底层的 sdk 用于数据传输,我负责的是 Adnroid 端的 sdk 开发. sdk 并不直接加载在 App 主进程,而是隔离在一个单 ...

  2. 记一次排查线上MySQL死锁过程,不能只会curd,还要知道加锁原理

    昨晚我正在床上睡得着着的,突然来了一条短信. 啥,线上MySQL死锁了,我赶紧登录线上系统,查看业务日志. 能清楚看到是这条insert语句发生了死锁. MySQL如果检测到两个事务发生了死锁,会回滚 ...

  3. MySQL数据库如何线上修改表结构

    一.MDL元数据锁 在修改表结构之前,先来看下可能存在的问题. 1.什么是MDL锁 MySQL有一个把锁,叫做MDL元数据锁,当对表修改的时候,会自动给表加上这把锁,也就是不需要自己显式使用. 当对表 ...

  4. 记一次线上bug排查-quartz线程调度相关

    记一次线上bug排查,与各位共同探讨. 概述:使用quartz做的定时任务,正式生产环境有个任务延迟了1小时之久才触发.在这一小时里各种排查找不出问题,直到延迟时间结束了,该任务才珊珊触发.原因主要就 ...

  5. NOI Day2线上同步赛崩盘记

    Preface 蒟蒻愉快的NOI线上赛Day2之行,不过因为太菜就凉了 这次由于策略&&网络的问题,最后两题都没有交,结果就靠T1稳住拿了75分就回家了. 我真是太菜了. 屠龙勇士 首 ...

  6. 解Bug之路-记一次线上请求偶尔变慢的排查

    解Bug之路-记一次线上请求偶尔变慢的排查 前言 最近解决了个比较棘手的问题,由于排查过程挺有意思,于是就以此为素材写出了本篇文章. Bug现场 这是一个偶发的性能问题.在每天几百万比交易请求中,平均 ...

  7. 记一次线上Curator使用过程JVM栈溢出解决

       为了同学们看起来一目了,特按如下思路进行讲解. 1.出现的场景    2.分析及解决的过程    3.总结 最近公司要使用zookeeper做配置管理(后面简称ZK),然后自己就提前用虚拟机进行 ...

  8. 记一次线上coredump事故

    1.事故背景 上周三凌晨,我负责的某个模块在多台机器上连续发生coredump,幸好发生在业务低峰期,而且该模块提供的功能也不是核心流程功能,所以对线上业务影响比较小.发生coredump后,运维收到 ...

  9. 记一次令人窒息的线上fullgc调优

    今天第二篇采坑了... ... 现场因为处理太急促没有保留,而且是一旁协助,没有收集到所有信息实在是有些遗憾...只能靠记忆回想一些细节 情况是一台服务器一启动就开始full gc,短短1分钟可以有几 ...

  10. 记Booking.com iOS开发岗位线上笔试

    今晚参加了Booking的iOS职位线上笔试,结束后方能简单归纳一下. 关于测试内容: Booking采用了HackerRank作为测试平台,测试总时长为75分钟,总计4道题. 测试之前我很紧张,因为 ...

随机推荐

  1. kubernetes configmap配置方法

    configmap解释 在k8s运行期间会有一些依赖的配置文件,他们存在宿主机中的一个目录中或者代码里,如果配置放生变化,pod在另一台服务器启动,在没有这些配置的时候,可能会导致pod无法正常运行, ...

  2. react减少组件渲染

    当this.setState()修改了state中的数据后,当前组件将重新渲染,同时也会重新渲染子组件,但只会渲染当前组件子树(当前组件以其所有子组件) shouldComponentUpdate 当 ...

  3. ubuntu server 22.04 安装docker

    ubuntu server 22.04 安装docker 官方安装文档: https://docs.docker.com/engine/install/ubuntu/ 1.更新软件列表: sudo a ...

  4. nginx虚拟主机实战

    基于nginx部署网站 虚拟主机指的就是一个独立的站点,具有独立的域名,有完整的www服务,例如网站.FTP.邮件等. Nginx支持多虚拟主机,在一台机器上可以运行完全独立的多个站点. 一.为什么配 ...

  5. java8 Lambda及Stream学习笔记

    Lambda表达式Lambda 表达式可以替代只有一个抽象函数的接口实现. Lambda表达式同时还提升了对集合.框架的迭代.遍历.过滤数据的操作. Lambda表达式使用场景任何有函数式接口的地方 ...

  6. rest接口list参数接收

    list<String> post传参 ["1112","2222","3332"]list<Object> pos ...

  7. Asp.net core Swashbuckle Swagger 的常用配置

    背景 .net core Swashbuckle Swagger 官方文档:https://github.com/domaindrivendev/Swashbuckle.AspNetCore 我们发现 ...

  8. typora中LaTeX公式常用指令

    # typora中LaTeX公式常用指令 以下指令只能保证在typora中完美显示,但是在其他编辑器中可能会部分不支持 \cal F.X.Y = KaTeX parse error: Expected ...

  9. typroa破解

    Typora 一款 Markdown 编辑器和阅读器 风格极简 / 多种主题 / 支持 macOS,Windows 及 Linux 实时预览 / 图片与文字 / 代码块 / 数学公式 / 图表 目录大 ...

  10. 2019南京区域赛ABCHJK题解 & KM-bfs(O(n^3))板子

    A.Hard Problem 题目大意:给你一个数n,然后让你计算一个子集大小,这个大小的子集要保证一定存在一个数是另一个数的约数,求出这个最小的数. 做法:显然后面的\(\frac{n}{2}\)个 ...