zl程序教程

您现在的位置是:首页 >  大数据

当前栏目

扒开Redisson的小棉袄,Debug深入剖析分布式锁之可重入锁No.1

分布式分布式 深入 剖析 debug Redisson 重入 NO.1
2023-06-13 09:15:32 时间

上次搭建好三主三从的redis cluster集群之后,也试了试redission的加锁解锁的API,那么redission是怎么实现分布式锁的呢?

我们就开始对这几行关键的代码进行分析,打好断点,debug调试,是分析源码,学习源码的一个好的方法,OK,让我们开始一场浪漫绚丽的源码探索之旅,redisson的源码写的很漂亮哦。

首先,我们主要去关注加锁的过程,那么对于redisson创建,通信等一些涉及底层的代码就只能忽略了,将注意力放到核心的流程代码上。对于getLock(String name)方法,

返回的是一个RedissonLock;看看RedissonLock是什么呢?

  1. 传入了一个异步处理的命令执行器,还有待上锁的name,构造函数中的this.id跟进去看,其实就是一个UUID对象,是当前要加锁客户端的唯一标识;
  2. 这里还有一个internalLockLeaseTime,从字面上来看,是一个内部的锁续约时间,默认是30000毫秒。
  3. this.entryName将UUID和传入的锁名称拼接起来,猜测一下,这个可能就是最终在Redis中要存储的加锁key。
  4. 下面进入加锁逻辑,核心代码很简单,就是lock.getLock(),他的运行逻辑是怎么样的呢?跟着debug的节奏往下走,我们来到了这里

5. 首先呢,先获取了当前线程的线程id,紧接着便调用了tryAcquire方法,尝试获取锁,跟进去看看,根据调用的名字可以猜测是一个异步执行的方法,但是能通过get()方法,将异步转成同步获取到执行结果。

tryAcquireAsync方法传入了leaseTime=-1,字面理解 续约时间,时间单位为秒,还有线程id,首先就进行了判断,判断这个leaseTime是否为-1,代码分为了俩个分支,为-1的代码貌似更多,那我们先来看为-1的情况,代码是如何走的。

可以看到他再次调用了一个异步方法tryLockInnerAsync(),其中传入的leaseTime变成从配置中获取的一个默认时间,通过调用了一个配置中的方法getLockWatchdogTimeout(),跟踪可以知道默认值正是30000毫秒,这个和之前的一个变量internalLockLeaseTime的默认值是一样的,还传入了线程id。这里我们可以留下一个疑问,从英文名来看,理解成看门狗超时时间,此刻我们可以去看看Redisson的官方文档,之前似乎在官方文档看到过对这个的介绍

If Redisson instance which acquired lock crashes then such lock could hang forever in acquired state. To avoid this Redisson maintains lock watchdog, it prolongs lock expiration while lock holder Redisson instance is alive. By default lock watchdog timeout is 30 seconds and can be changed through Config.lockWatchdogTimeout setting.大概意思是说,redisson实现了一种看门狗的机制,当redisson实例获得锁之后一直保持活跃的时候,可以延长其加锁时间,当其崩溃的时候也能防止持有的锁一直保持,有自动释放机制。这个看门狗看起来是个挺核心的组件,我们可以先把加锁流程看下去,再看看这个看门狗到底是怎么运行的。

下面我们看到了一段核心之核心的代码,我一看到就觉得,是他了,

这里他使用了lua脚本去实现了一段逻辑,也是异步操作,我们就Debug一下,一点点分析下他究竟干了什么?

  1. 首先呢,他先用exists命令判断了待获取锁的key anyLock 存不存在,如果不存在,就使用hset命令将锁key anyLock作为key的map结构中存入一对键值对,37f75873-494a-439c-a0ed-f102bc2f3204:1 1;
  2. 同时还使用了pexpire命令给anyLock设置了过期时间30000毫秒,然后返回为空;
  3. 如果anyLock已经存在了,会走另一个分支,此时会判断anyLock Map中是否存在37f75873-494a-439c-a0ed-f102bc2f3204:1,如果存在的话,就调用hincrby命令自增这个key的值,并且将anyLock的过期时间设置为30000毫秒,并且返回空。
  4. 如果上面俩种情况都不是,那么就返回这个anyLock的剩余存活时间。

分析完这个lua脚本之后,对具体redisson如何加锁的逻辑就有了一定的认知,lua脚本也可以保证执行命令的原子性。然后呢就直接返回了一个RFuture ttlRemainingFuture,并且给他加了一个监听器,如果当前的这个异步加锁的步骤完成的时候调用,如果执行成功,就直接同步获取一个Long类型的ttlRemaining。此时我们发现TTLRemaining是为null的,那么就会执行下面的这一行代码,我们可以看到注释 锁已获得。

// lock acquired

if (ttlRemaining == null) {

scheduleExpirationRenewal(threadId);

}

这一行代码看起来就不同凡响,貌似是一个调度任务,想想之前看的官方文档里说的那个自动延长锁的周期,会不会和这个有关呢?这个我们可以下面继续去跟随代码执行,分析出其中的关联。

夜已经深了,就先执笔到此啦,更多的学习和整理后面会继续分析,如果文中有错误,欢迎小伙伴批评指正,祝君好梦。晚安