大家好!
提到分布式锁,大家一般都会想到 Redis。
想到 Redis,一部分同学会说到 Redisson。
那么说到 Redisson,就不得不掰扯掰扯一下它的“看门狗”机制了。
所以你以为这篇文章我要给你讲“看门狗”吗?
不是,我主要是想给你汇报一下我最近研究的由于引入“看门狗”之后,给 Redisson 带来的两个看起来就菊花一紧的 bug :
-
看门狗不生效的 BUG。
-
看门狗导致死锁的 BUG。
为了能让你丝滑入戏,我还是先简单的给你铺垫一下,Redisson 的看门狗到底是个啥东西。
看门狗描述
你去看 Redisson 的 wiki 文档,在锁的这一部分,开篇就提到了一个单词:watchdog
github.com/redisson/re…
watchdog,就是看门狗的意思。
它是干啥用的呢?
好的,如果你回答不上来这个问题。那当你遇到下面这个面试题的时候肯定懵逼。
面试官:请问你用 Redis 做分布式锁的时候,如果指定过期时间到了,把锁给释放了。但是任务还未执行完成,导致任务再次被执行,这种情况你会怎么处理呢?
这个时候,99% 的面试官想得到的回答都是看门狗,或者一种类似于看门狗的机制。
如果你说:这个问题我遇到过,但是我就是把过期时间设置的长一点。
时间到底设置多长,是你一个非常主观的判断,设置的长一点,能一定程度上解决这个问题,但是不能完全解决。
所以,请回去等通知吧。
或者你回答:这个问题我遇到过,我不设置过期时间,由程序调用 unlock 来保证。
好的,程序保证调用 unlock 方法没毛病,这是在程序层面可控、可保证的。但是如果你程序运行的服务器刚好还没来得及执行 unlock 就宕机了呢,这个你不能打包票吧?
这个锁是不是就死锁了?
所以......
为了解决前面提到的过期时间不好设置,以及一不小心死锁的问题,Redisson 内部基于时间轮,针对每一个锁都搞了一个定时任务,这个定时任务,就是看门狗。
在 Redisson 实例被关闭前,这个狗子可以通过定时任务不断的延长锁的有效期。
因为你根本就不需要设置过期时间,这样就从根本上解决了“过期时间不好设置”的问题。默认情况下,看门狗的检查锁的超时时间是 30 秒钟,也可以通过修改参数来另行指定。
如果很不幸,节点宕机了导致没有执行 unlock,那么在默认的配置下最长 30s 的时间后,这个锁就自动释放了。
那么问题来了,面试官紧接着来一个追问:怎么自动释放呢?
这个时候,你只需要来一个战术后仰:程序都没了,你觉得定时任务还在吗?定时任务都不在了,所以也不会存在死锁的问题。
搞 Demo
前面简单介绍了原理,我也还是给你搞个简单的 Demo 跑一把,这样更加的直观。
引入依赖,启动 Redis 什么的就不说了,直接看代码。
示例代码非常简单,就这么一点内容,非常常规的使用方法:
把项目启动起来,触发接口之后,通过工具观察 Redis 里面 whyLock 这个 key 的情况,是这样的:
你可以看到在我的截图里面,是有过期时间的,也就是我打箭头的地方。
然后我给你搞个动图,你仔细看过期时间(TTL)这个地方,有一个从 20s 变回 30s 的过程:
首先,我们的代码里面并没有设置过期时间的动作,也没有去更新过期时间的这个动作。
那么这个东西是怎么回事呢?
很简单,Redisson 帮我们做了这些事情,开箱即用,当个黑盒就完事了。
接下来我就是带你把黑盒变成白盒,然后引出前面提到的两个 bug。
我的测试用例里面用的是 3.16.0 版本的 Redission,我们先找一下它关于设置过期动作的源码。
首先可以看到,我虽然调用的是无参的 lock 方法,但是它其实也只是一层皮而已,里面还是调用了带入参的 lock 方法,只不过给了几个默认值,其中 leaseTime 给的是 -1:
而有参的 lock 的源码是这样的,主要把注意力放到我框起来的这一行代码中:
tryAcquire 方法是它的核心逻辑,那么这个方法是在干啥事儿呢?
点进去看看,这部分源码又是这样的: