Redisson看门狗模式死锁问题

业务背景:

1、项目需要对其他业务系统提供同步业务状态接口,该业务状态更新在项目自身很多业务场景都会有更新操作,在所有业务接口上都是加redis并发锁。

2、项目使用3.17.0版本的redisson依赖包,其中redis使用lettuce客户端。

问题描述

        测试同学反馈从APP上测试一直提示系统繁忙,请稍后重试,这错误不是我自己定义的吗?没有获取到并发锁返回错误信息。redis客户端界面查询对应业务锁,锁一直存在,如下截图。是以hash值存入redis的。看后续源码。hash里的row=1的key、value值看起来很奇怪!!!

疑问:

1、hash里的key值是指啥?线程id?截图中key前缀像是uuid和数字组成。

2、value是int类型数字,指是啥?怎来的?

单看redis里存的值,看的很懵逼!!!

源码:

1、业务接口:

业务接口写do-while循环三次来处理业务逻辑,为啥加循环,因为其他平台只是同步一次数据,有可能会出现其他平台同步数据同时,项目自身业务已经占有锁,避免数据丢失和死循环,所以加上循环等待获取并发锁,业务接口源码如下(小声逼逼:业务代码已被抠掉了):

截图代码问题有俩处:

第一点:获取到并发锁后,执行业务代码逻辑,并没有跳出循环;

第二点:循环执行4次,并非是3次。

2、Redisson获取并发锁

(1)业务dao层封装

备注:redissonClient是通过注解注入的,代码如下:

@Resource

private RedissonClient redissonClient;

(2)RedissLock类tryLock无参方法

        

(3)RedissLock类tryLockAsync无参方法,

(4)RedissLock类tryLockAsync(long threadId)方法,这方法没啥好说的,很简单赋值;

(5)RedissLock类tryAcquireOnceAsync方法,重点!重点!重点!

(6)RedissLock类tryLockInnerAsync方法,执行lua脚本,判断当前key在redis里是否存在,若存在,则value+1;

备注:getLockName方法得到字符串:87ffb241-1906-4468-9014-3fda98edb858:133;133是指当前执行业务线程id,冒号前面字符串是指连接redis线程id。

(6)RedissBaseLock类scheduleExpirationRenewal(long threadId)方法

备注:scheduleExpirationRenewal方法中try里finally执行判断有点意思。在本地断点调试时,过了很久才进入这个方法,最后执行完成后,redis并没有出现死锁情况。看这里才搞明白,finally会判断当前线程是否已中断,中断会自动释放锁

(6)RedissBaseLock类renewExpiration()是实现看门狗模式,重中之重!!!(想不到其他词来形容0^0)

3、Redisson释放并发锁

(1)业务dao层代码

        isLocked()是判断当前锁是否已释放,isHeldByCurrentThread判断当前并发锁是否属于当前线程

(2)RedissonLock类的unlock无参方法

(3)RedissonBaseLock类的unlockAsync(long threadId)方法,其中unlockInnerAsync方法释放redis的锁(使用lua脚本,下一步详细解释),当释放成功后,会将刷新过期时间定时任务取消cancelExpirationRenewal(threadId)方法。

(4)RedissonLock类unlockInnerAsync(long threadId)方法,lua脚本释放并发锁,这里释放逻辑并不是直接删除,是将redis中hash数据里的key对应value减去1。

(5)RedissBaseLock类cancelExpirationRenewal方法,释放锁和取消定时任务

疑问回答

         Redisson中看门狗存入redis数据格式是hash。hash名称是业务定义的redis的key,hash中的key是指客户端连redis线程id+冒号+业务线程id,value是一个计数,如果正常业务value值应该是1。按业务接口代码写代码逻辑:在同步一个线程里,循环获取锁,锁会正常获取,并不会返回false,根据上述获取锁的lua源码可以看出来,如果锁已存在,value会加1,释放锁是对应value减1,循环获取4次锁,finally里释放一次锁,最终业务线程结束后,redis里的锁并没有删除调,还是会有定时任务刷新redis锁的过期时间。这就是会出现死锁情况。此外,在断点调试时,业务线程执行完成后,Redis里不会有锁存在,大概因为断点调试停留时间过程,业务线程停留太长时间,线程中断,导致走了取消定时任务刷新逻辑(方法:cancelExpirationRenewal),只是猜测,并没有验证。

总结:

        使用Redisson的看门狗模式要慎重,在业务代码使用循环获取并发锁,慎重慎重慎重!!以上存在问题的地方,欢迎各位大佬指出来。0.0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值