那么附加题就来了。
附加题:阅读上面材料后,当默认时间被修改为 60s 后,那么每当 key 的 ttl(剩余时间) 返回多少的时候,会进行续命操作?
答:由题可得,时间每过 60s/3 = 20s 时,任务会被触发,看门狗进行工作。
所以,60s -20s =40s。每当 key 的 ttl 返回 40 时,会进行续命操作。
得学会变形,朋友们,明白吗?
接下来,我们看看这个 task 任务是怎么实现的。
可以看到,这个 Timeout 是 netty 包里面的类。
这个 task 任务是基于 netty 的时间轮做的。
面试官追问你:啥是时间轮?
你又不知道。那你接着往下看。
时间轮又是啥?
你听到了时间轮,你首先想到了啥?
听到这个词,就算你完全不知道时间轮,你也该想到,轮子嘛,不就是一个环嘛。
网上随便一搜,你就知道它确实长成了一个环状:
它的工作原理如下:
图片中的时间轮大小为 8 格,每格又指向一个保存着待执行任务的链表。
我们假设它每 1s 转一格,当前位于第 0 格,现在要添加一个 5s 后执行的任务,则0+5=5,在第5格的链表中添加一个任务节点即可,同时标识该节点round=0。
我们假设它每 1s 转一格,当前位于第 0 格,现在要添加一个 17s 后执行的任务,则(0+17)% 8 = 1,则在第 1 格添加一个节点指向任务,并标记round=2,时间轮每经过第 1 格后,对应的链表中的任务的 round 都会减 1 。则当时间轮第 3 次经过第 1 格时,会执行该任务。
需要注意的是时间轮每次只会执行round=0的任务。
知道了工作原理,我们再看看前面说的 Timeout 类,其实就是 HashedWheelTimer 里面 newTimeout 方法的返回:
前面我们分析了,在 Redssion 实现看门狗功能的时候,使用的是 newTimeout 方法。该方法三个入参:
1.task,任务,对于 Redssion 看门狗功能来说,这个 task 就是把对应的 key 的过期时间重置,默认是 30s。
2.delay,每隔多久执行一次,对于 Redssion 看门狗功能来说,这个 delay 就是 internalLockLeaseTime/3 算出来的值,默认是 10s。
3.unit,时间单位。
其实,你发现了吗,这个时候我们已经脱离了 Redssion 进入 Netty 了。
我们只需要告诉 newTimeout 方法,我们要每隔多少时间执行一次什么任务就行。
那我们为什么不自己写个更加简单的,易于理解的 Demo 来分析这个时间轮呢?
比如下面这样的:
上面的 Demo 应该是很好理解了。
到这里,我们知道了看门狗是基于定时任务实现的,而这个定时任务是基于 Netty 的时间轮实现的。
对于 HashedWheelTimer 的源码,开始我还想进行一个导读,写着写着去查阅资料的时候发现,这个链接里面的对于源码的解读已经很到位了,我索性把自己的写那部分删除了,大家有兴趣的可以去阅读一下:
https://www.jianshu.com/p/1eb1b7c67d63
另外,关于时间轮,还可以看一下 IBM 论坛里面的这篇文章《浅析 Linux 中的时间编程和实现原理》:`
https://www.ibm.com/developerworks/cn/linux/1308_liuming_linuxtime3/index.html
解锁操作
还记得我们加锁操作的时候说的吗?
进入,然后加一,你联想到了什么? 这不就是可重入锁吗! 看到这里的时候,解锁的 lua 脚本都不必看的,想也能想到,肯定是有一个减一的操作,然后减到 0,就释放这把锁。 一会我们就去验证这个点。
这一小节,我们就去验证这个点,请看下面的释放锁执行的 lua 脚本:
是不是里面有个 counter 的判断,如果减一后小于等于 0。就执行 del key 的操作。
解锁操作确实挺简单,主要是 del 之后执行了一个 publish 命令。你猜这里 publish 的是啥?
先猜再验证嘛,大胆假设,小心求证!
这里是基于 redis 的发布/订阅功能。解锁的时候发布了一个事件,你觉得通知的是什么玩意?
肯定是告诉别的线程,我这边锁用完了,你来获取吧。
别的线程是什么线程呢?
就是想要申请同一把锁的线程。
tryAcquire 的代码我们之前分析过,当 ttl 不为 null 时,只有一种情况,那就是加锁失败:
所以加锁失败的线程就执行了 subscribe 方法,完成了订阅。
这样,就和释放锁时的 publish 操作呼应上了。
接下来就只剩下一个问题没有解决了:怎么让看门狗知道不用续命了?
其实就是在执行完解锁的 lua 脚本之后,通过响应式编程,完成了 cancel 操作。
自此,我们的加锁、看门狗续命、解锁的一套操作就完成了。
补充说明,顺便打脸
在打脸之前,我先问个问题吧:看门狗什么情况下会失效?
别给我说宕机,宕机之后,由于线程没了,看门狗只是不续命了, redis 里面的 key 到期之后就删除了。
我问的失效是指什么时候完全就不启动?
答案是,调用 lock 方法的时候传进一个指定时间,这样如果指定时间之内没有调用 unLock 方法,该锁还是会被释放的。就像下面这样:
rLock.lock(5,TimeUnit.SECONDS);
该锁在 5s 之后就会自动释放了。不会进行续命操作!!!
对应的源码如下,注意看我写的注释:
所以,我想起很久之前我在群里说的这个,红框框起来的部分是错的:
明确指定了超时时间的时候,是不会启动看门狗机制。
自己打自己脸的事......
好爽啊,这事我经常干。
而且,读书人的事,这能叫打脸吗?这叫成长。
另外,这图画的挺好的,分享给大家:
图片来源:https://juejin.im/post/5bf3f15851882526a643e207
还有一个读者提出的问题,续租的时候,是否需要进行次数的限制?
我觉得是不需要限制的,如果你的代码一直在进行续期操作,说明两种情况:
1.由于某种异常原因,导致你本次需要处理的数据比之前的多,所以,需要的时间更长,导致一直在进行续期操作。
2.你的代码有问题,导致了死循环,也就是死锁的出现,这个锅,Redssion 不背。
最后,还有一个问题,这锁安全吗,或者说你觉得会有什么问题?
什么?你不知道?
之前分享过的文章中说过了:
节点之间异步通信,会出现上面描述的情况。所以 Redis 推出的解决方案是啥?
RedLock,之前写的《求锤得锤之神仙打架》这篇文章,就是讲 RedLock 的。如果你不知道,你就去瞅一眼。
其实后来有一天我突然想到, 如果从 CAP 的角度上去看 Redis 分布式锁问题,我觉得可能更好理解一点。
分布式锁的一致性要求 CP,但是Redis 集群架构之间的异步通信满足的是 AP ,因此对不上呀,就是有问题的啊。
但是为什么 Redis 做分布式锁还是那么流行呢?
可能是因为大多场景中可以容忍它的这个问题,也可能是使用者存在侥幸心理吧,或者说使用者就当个黑盒使用,根本不知道可能会出问题。
最后说一句(求关注)
写完之后一看时间又是凌晨 2 点过了:
点个“在看”吧,周更很累的,不要白嫖我,需要一点正反馈。
才疏学浅,难免会有纰漏,如果你发现了错误的地方,还请你留言给我指出来,我对其加以修改。(我每篇技术文章都有这句话,我是认真的说的。)
感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。
我是why技术,一个不是大佬,但是喜欢分享,又暖又有料的四川好男人。
欢迎关注公众号【why技术】,坚持输出原创。分享技术、品味生活,愿你我共同进步。