redission看门狗实现机制一看就懂

某次偶然听到redission看门狗,感觉比较有趣,于是就想看看它长啥样。。。。废话不多说,直入正题。

什么是看门狗?
用官方文档的话来说就是:

大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

首先先看看这部分实例的代码,相信大家都能看懂
在这里插入图片描述
看了这部分代码之后,探究之前再思考一个问题:

问题:redission实现的分布式锁和我们自己造的轮子有什么区别?

如果是自己造轮子基于redis实现加锁和解锁的话,它的实现如下:

  • 加锁:原子命令加锁(实际就是向redis用setnx原子命令设置一个随机值)
  • 解锁:释放锁的时候检查这个值是否存在,存在就删除,但是这个步骤包含两个操作,需要保证这两步操作的原子性,1.通过key获取这个随机值,判断这个值是否存在 2.删除这个值。

如果不保证原子性会发生什么问题呢?这里假设一种情况:

  1. 线程A准备释放锁,首先要获取这个锁,获取到锁,正准备删除
  2. 但此时因为一些原因导致锁超时,如程序GC导致了STW,没来得及删除,锁过期了
  3. STW之后,线程B进来加了同样key的锁,此时线程B还没执行完,线程A又执行了刚刚没有执行完的命令,把线程B的锁删除了,这就出问题了。

那redission又是如何实现加锁和解锁的呢?
(1)如何加锁:对于这个问题,首现确定向redis中set进一个值这一步肯定是框架帮我们生成了,所以我们要想验证这种情况,除了从源码中查看,也可以直接程序跑起来,到reids管理界面看一下
在这里插入图片描述
可以看到这是一个hash类型的值,key是一个随机值(UUID:线程id),value随便弄了一个1进去。

而通过debug 源码发现通过tryLockInnerAsync方法发送了一段lua脚本,当加锁成功后会返回null,注意这个返回null后文会用到.在这里插入图片描述
而这个getLockName方法就是我们看到的key值
在这里插入图片描述
我们顺带点进去看看evalWriteAsync这个方法
在这里插入图片描述

script:是要执行的 lua 脚本。
keys:是 redis 中的 key。这里的 why 就是脚本中的 KEYS[1]。
params:是 lua 脚本的参数。这里的 30000 就是脚本中的 ARVG[1]。UUID:thredId 就是 ARVG[2]。

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

让我们回过头看看这段脚本的含义:
在这里插入图片描述

第一部分:加锁

  • 首先用 exists 判断了 KEYS[1] (即 why)是否存在。
  • 如果不存在,则进入第 5 行,使用 hincrby 命令创建一个新的哈希表,如果域field不存在,那么在执行命令前会被初始化为0,此命令的返回值就是执行hincrby命令后,哈希表key中域field的值,此时进行increment,也就是返回1
  • 之后进入第6行,对KEY[1]设置过期时间,30000ms
  • 然后返回nil

第二部分:重入

  • 首先判断KEY[1]是否存在,因为KEY[1]是一个hash结构,所以13行意思是获取这个KEYS[1]中字段为ARGV[2]也就是UUID:thredId这个值是否存在
  • 如果存在进入14行代码对其进行加1操作(锁重入)
  • 然后进入15行重新设置过期时间30s
  • 然后返回nil

第三部分:返回

  • 作用就是返回 KEY[1] 的剩余存活时间

(2)如何解锁:使用了lua脚本+Redis单线程
为什么 lua 脚本可以解决这个问题呢?
因为 lua 脚本的执行是原子性的,它会将这获取这个值和删除值两个操作放到一个脚本中,当成一个命令去执行,再加上 Redis 执行命令是单线程的,所以在 lua 脚本执行完之前,其他的命令都得等着。就不会出现上面说的情况了。

刚看完了加锁操作的lua脚本,来看解锁操作的lua脚本也就很清晰明了了
在这里插入图片描述

  • 首先判断KEYS[1]是否存在
  • 存在将值减1,如果counter还大于0,就重新设置过期时间30000ms,否则就删除操作

可以看到删除过后还执行了一个publish命令,其实这里是基于redis的一个发布/订阅功能,解锁的时候发布了一个事件,通知其他线程,我这边锁用完了,你们可以用了,那其他线程是什么线程呢?也就是订阅了这把锁的线程
在这里插入图片描述

这里可以看到当ttl不等于null的时候也就是加锁失败,加锁失败的线程,都会去执行subscribe方法,这里就和publish对应上了

以上就是redission加锁和解锁的一个实现原理,讲了怎么多那看门狗机制怎么实现的呢?

当我们调用lock方法后都要调用下面这个方法:

org.redisson.RedissonLock#tryAcquireAsync

在这里插入图片描述

scheduleExpirationRenewal方法从字面上意思就很容易理解到期续订,也就是看门狗的具体实现。那什么情况下走到else这个条件呢,也可以理解成什么情况下开启看门狗呢?
答:首先leaseTime要==-1,这个leaseTime也就是设置的锁过期时间,也就是说如果我们调用的lock方法传入超时时间限制,也就不会开启开门狗。

lock.lock(); 开启看门狗
lock.lock(5000, TimeUnit.SECONDS); 不开启看门狗

其次ttlRemaining==null,这个ttlRemaining也就是加锁成功后上文提到的返回的null值。

再次debug进入这个方法后,会进入到下面这个方法

org.redisson.RedissonLock#renewExpiration

在这里插入图片描述

很明显,从上面标注的数字可以看出来:
①:这是一个定时任务。
②:这任务需要执行的核心代码。
③:该任务每 internalLockLeaseTime/3ms 后执行一次。而 internalLockLeaseTime 默认为 30000。所以该任务每 10s 执行一次。

在看一下第二步干的什么
在这里插入图片描述

所以,每当 key 的 ttl(剩余时间)为 20 的时候,则进行续命操作,重新将 key 的过期时间设置为默认时间 30s,当然这个internalLockLeaseTime值也是可以修改的,如果改成60秒,那么每当 key 的 ttl 返回 40 (60 -60/3)时,会进行续命操作
在这里插入图片描述

写在最后的话:

到这里看门狗的具体实现也就清楚了,无非是后台起一个定时任务的线程,每隔一定时间对该锁进行续命,延长锁的时间,很多人肯定好奇,那延长锁的次数是有限制的吗?难道无限进行续命吗,假设业务一直没执行完,难道锁一直不释放吗?起初我也有这样的疑问,但是想了想,实际业务中也不能发生这样的情况,除非是代码bug,或者陷入了死循环,所以这也不能怪到redission上面。

参考文章:
1.https://github.com/redisson/redisson/wiki/目录
2.https://juejin.cn/post/6844904106461495303

  • 24
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
Redission是一个基于Redis的分布式Java对象和数据结构的框架。它提供了一系列的功能和特性,其中就包括了Redission看门狗Redission Sentinel)。 Redission看门狗Redission框架中的一部分,它是用来监控和管理Redis Sentinel集群的。Redis Sentinel是Redis的高可用性解决方案,通过运行多个Redis Sentinel进程来监控Redis主从节点的状态,当主节点出现故障时,自动选举新的主节点。 Redission看门狗使用Redis Sentinel提供的API来实现Redis Sentinel集群的监控和管理。它可以自动发现和监控Redis Sentinel集群中的节点,并在主节点故障时进行故障转移。此外,Redission看门狗还提供了一些其他的功能,如集群拓扑更新、故障转移监听、自动重连等。 要使用Redission看门狗,你需要在你的Java应用中引入Redission的依赖,并配置Redis Sentinel集群的相关信息。然后,你可以使用Redission提供的API来访问和操作Redis Sentinel集群中的数据和对象。 以下是一个简单的示例代码,演示了如何使用Redission看门狗来监控和管理Redis Sentinel集群: ```java Config config = new Config(); config.useSentinelServers() .setMasterName("mymaster") .addSentinelAddress("redis://127.0.0.1:26379", "redis://127.0.0.1:26380") .setDatabase(0); RedissonClient redisson = Redisson.create(config); // 监听Redis Sentinel集群的状态 redisson.getSentinelConnection().addListener(new RedisMasterSlaveDownUpEventListener() { @Override public void onMasterSlaveUp(String masterName, String slaveName) { System.out.println("Master " + masterName + " is up, slave " + slaveName + " is up"); } @Override public void onMasterSlaveDown(String masterName, String slaveName) { System.out.println("Master " + masterName + " is down, slave " + slaveName + " is down"); } }); // 获取Redis Sentinel集群中的某个键值对 RBucket<String> bucket = redisson.getBucket("mykey"); String value = bucket.get(); // 设置Redis Sentinel集群中的某个键值对 bucket.set("myvalue"); // 关闭Redis连接 redisson.shutdown(); ``` 这只是一个简单的示例,你可以根据自己的实际需求,使用Redission看门狗来监控和管理Redis Sentinel集群中的数据和对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值