面试时遇到『看门狗』脖子上挂着『时间轮』,我就问你怕不怕? (2)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 面试时遇到『看门狗』脖子上挂着『时间轮』,我就问你怕不怕? (2)

可以看到,在这里的时候,获取到的 thredId 就是 1。那 key 里面 UUID 后面拼接的 1。是不是就是这里来的呢?我们接着往下看。


再往前 Debug 三步就能到下面的这个位置:


org.redisson.RedissonLock#tryLockInnerAsync


到这里的 getLockName(threadId) 其实就是我们要找的东西:


你看,这一串东西,不就是我们刚刚看到的 UUID:1 吗?这个 1 就是线程ID。

什么?你问我为什么说这个 id 是 UUID?


直觉,程序猿的直觉告诉我,这就是个 UUID。但是我可以给你验证一下。


这个 id 的来源是下面这个接口:


org.redisson.connection.ConnectionManager

而该接口有 5 个实现类:


在创建 ConnectionManager 时,每个实现类的构造方法传的都是 UUID。


所以,我们可以下结论了:


使用 Redssion 做分布式锁,不需要明确指定 value ,框架会帮我们生成一个由 UUID 和 加锁操作的线程的 threadId 用冒号拼接起来的字符串。


毫无挑战甚至有点无聊的探索过程啊。(其实我想表达的是源码真的不难,不要抱有恐惧的心理,带着问题去看源码。)


但是别着急,这只是开胃菜。


对于第二个问题:为什么要用 Hash 类型,而不用 String 类型呢?


我们在下一节,寻找过期时间去哪里了的同时,寻找该问题的答案。


过期时间去哪了?


这个问题,我们从这段代码里面可以找到答案:


org.redisson.RedissonLock#tryLockInnerAsync


我们首先看一下这个方法对应的几个入参:


主要关注我框起来的部分:


script:是要执行的 lua 脚本。


keys:是 redis 中的 key。这里的 why 就是 KEYS[1]。


params:是 lua 脚本的参数。这里的 30000 就是 ARVG[1]。UUID:thredId 就是 ARVG[2]。


所以这个过期时间我们也知道了,默认是 30000ms,即30s。


知道了上面三个参数的含义后,我们再来拆解这个 lua 脚本就很简单了,首先我们把他拆解为三部分:


第一部分:加锁


先看第一部分的加锁操作:


第 4行,首先用 exists 判断了 KEYS[1] (即 why)是否存在。


如果不存在,则进入第 5 行,使用 hincrby 命令。hincrby 命令是干什么的知道吧?


之后进入第 6 行,对 KEY[1] 设置过期时间,30000ms。

然后,第7行,进行返回为 nil,结束。


这样,一个原子性的加锁操作就完成了。


到这里,我们就已经从源码的角度验证了:因为用的是 hincrby 命令,Redssion 做锁的时候 key 确实是一个 Hash 结构。


第二部分:重入


当第一部分的 if 分支判断 KEYS[1] 是存在的,则会进入到这个分支中:


由于 KEYS[1] 是一个 Hash 结构,所以第 13 行的意思是获取这个 KEYS[1] 中字段为 ARGV[2] 的数据,判断是否存在。


如果存在,则进入第 14 行代码,用 hincrby 命令对 ARGV[2] 字段进行加一操作。


然后第 15 行,没啥说的,就是重新设置过期时间为 30s。之后第 16 行,返回为 nil,结束


所以,你在感受一下第 14 行代码的作用是什么?进入,然后加一,你联想到了什么?


看到这里的时候,解锁的 lua 脚本都不必看的,想也能想到,肯定是有一个减一的操作,然后减到 0,就释放这把锁。一会我们就去验证这个点。


所以,这里也就解释了为什么 Redssion 需要用 Hash 类型做锁。因为它支持可重入呀。

你用 String 类型,你怎么实现重入功能,来键盘给你,实现一个,让我学习一下?(其实也是可以的,就是有点背道而驰了。没意义。)


第三部分:返回


一行代码,问题不大。作用就是返回 KEY[1] 的剩余存活时间


通过分析 lua 的这三部分,我们知道了:过期时间默认是 30s。当一个 key 加锁成功或者当一个锁重入成功后都会返回空,只有加锁失败的情况下会返回当前锁剩余的时间。


记住这个结论,我们在接下来的看门狗咋工作的这一小节中会用到这个返回值。


另外,写文章的时候我发现 Redssion 的最新版本 3.12.3 和之前的版本相比,加锁时的 lua 脚本有一个细微的差别,如下:


3.12.3 版本之前用的是 hset ,现在用的是 hincrby。所以导致第一部分和第二部分相似度有点高。看起来会有点容易迷糊。


你去网上找应该看到的都是说 hset 操作的。因为 3.12.3 版本刚刚发布一个月。


恭喜你,朋友,又学到了一个用不上的知识点。


看门狗咋工作的?


看到这一节的朋友们,辛苦了。在这一节,我们终于要看到看门狗长啥样了。


org.redisson.RedissonLock#tryAcquireAsync


这里的 ttlRemaining 就是经过 lua 脚本后返回的值。经过前面我们知道了,当加锁成功或者重入成功后会返回 null。进入这个方法:


org.redisson.RedissonLock#scheduleExpirationRenewal


这个方法,就是看门狗工作的片区了。



Debug之后,你会遇到这个方法:

org.redisson.RedissonLock#renewExpiration



很明显,从上面标注的数字可以看出来:


①:这是一个任务。


②:这任务需要执行的核心代码。


③:该任务每 internalLockLeaseTime/3ms 后执行一次。而 internalLockLeaseTime 默认为 30000。所以该任务每 10s 执行一次。


接着我们看一下 ② 里面执行的核心代码是什么:


这个 lua 脚本,先判断 UUID:threadId 是否存在,如果存在则把 key 的过期时间重新设置为 30s,这就是一次续命操作。


来,在做个小学二年的算法题:


应用题:key 默认的过期时间是 30s,每过 30s/3 的时候会去进行续命操作,那么每当 key 的 ttl(剩余时间)返回多少的时候,会进行续命操作?


答:由题干可知,30s/3 = 10s。于是得公式到:30s - 10s =20s。


所以,每当 key 的 ttl(剩余时间)为 20 的时候,则进行续命操作,重新将 key 的过期时间设置为默认时间 30s。


注意我上面一直强调的是默认时间 30s


因为这个时间是可以修改的,比如我们想要修改为 60s,就这样:


于是 internalLockLeaseTime 就变成了 60000 了:

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
4月前
|
搜索推荐 Windows
让你的电脑准时“打个盹”:Win10定时休眠
木头左教你设置Windows 10任务计划程序,让电脑定时休眠,节约能源又呵护健康。首先确保休眠功能开启,然后在任务计划程序创建新任务,命名如“定时休眠”,设置触发时间和操作(cmd.exe /c shutdown -h)。可高级定制,如条件触发或异常处理。跟着步骤实践,解决常见问题,打造个性化自动休眠计划。记得谨慎操作哦!
|
算法 Cloud Native
【刷题日记】2039. 网络空闲的时刻
本次刷题日记的第 9 篇,力扣题为:2039. 网络空闲的时刻 ,中等
太难了!面试官居然要我停止一个正在运行的线程?
停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作。停止一个线程可以用Thread.stop()方法,但最好不要用它。虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且是已被废弃的方法。
|
存储 NoSQL
3.10.0-693.5.2内核nfs客户端租约过期挂死问题分析
## 现象 1. 边缘存储两个节点fileserver,glance挂载物理机上的挂载点均出现挂住无法访问 2. 从客户端抓包看,客户端内核间隔5s向服务端发送renew租约请求,服务端返回NFS4ERR_EXPIRED,即租约过期错误,从抓包现象看客户端一直向服务端发送相同的clientid renew请求,服务端一直返回租约过期错误,导致挂载点无法恢复 ![](https://ata2-img
1614 1
3.10.0-693.5.2内核nfs客户端租约过期挂死问题分析
|
监控 网络协议 Dubbo
开工第一天,这个超时问题把我干趴下了!
开工第一天,这个超时问题把我干趴下了!
|
NoSQL 安全 Java
面试时遇到『看门狗』脖子上挂着『时间轮』,我就问你怕不怕? (1)
面试时遇到『看门狗』脖子上挂着『时间轮』,我就问你怕不怕? (1)
132 0
|
NoSQL 安全 Linux
面试时遇到『看门狗』脖子上挂着『时间轮』,我就问你怕不怕? (3)
面试时遇到『看门狗』脖子上挂着『时间轮』,我就问你怕不怕? (3)
255 0
|
程序员
空闲时间请大家不要接私活,要提升自己!
  现在社会,有很多人都在利用个人时间兼职赚钱,程序员俗称“接私活”,其他行业称作兼职,比如下了班出去跑滴滴,周末兼职抢单送外卖等等,都是普通人很常见的兼职方式。   甚至很多技术同行,我听说也有周末去跑滴滴和送外卖的,我觉得很不可思议,然而我的观点是如果你想成为成功人士,高收入人群那么你不应该去做兼职,不要把你宝贵的时间,浪费在兼职上。   简单经济学分析   涉及到收入,文字洗脑显得很空洞了就,那么今天我给你来一个简单的经济学论调。   我们看一个案例,李嘉诚是香港首富,但是李嘉诚会亲自修剪自家的草坪吗?除非某一天他想体验一下劳动乐趣什么的,否则一定是请人来修剪草坪。
145 0
|
Web App开发 SQL Java
艾伟_转载:一次挂死(hang)的处理过程及经验
前言:        CPU占用率低,内存还有许多空余,但网站无法响应,这就是网站挂死,通常也叫做hang。这种情况对于我这样既是CEO,又是CTO,还兼职扫地洗碗的个人站长来说根本就是家常便饭。以下是一次处理hang的经验及总结,前后用了一个月,不仅涉及程序排查,数据库优化,还有硬件升级的苦恼。
1641 0
|
Web App开发 SQL Java
艾伟:一次挂死(hang)的处理过程及经验
前言:        CPU占用率低,内存还有许多空余,但网站无法响应,这就是网站挂死,通常也叫做hang。这种情况对于我这样既是CEO,又是CTO,还兼职扫地洗碗的个人站长来说根本就是家常便饭。以下是一次处理hang的经验及总结,前后用了一个月,不仅涉及程序排查,数据库优化,还有硬件升级的苦恼。
1673 0