redisson分布式锁的执行源码分析记录一下

很简单的开头

  • 之前在项目中有使用redisson作为分布式锁的实现方式,就想着看看它的源码的执行流程,然后记录一下。

乱排版的中间

  • 废话不多说,先看一下项目中redisson使用方法:
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.3</version>
</dependency>

redisson分布式锁的执行源码分析记录一下_第1张图片

redisson分布式锁的执行源码分析记录一下_第2张图片

  • 上图是redisson的基本用法。
  • 这里先提一下关于使用分布式锁的几个关键点,这样可以在阅读redisson源码时根据提出的设想看看它是如何实现的?
    1 对加锁和设置过期时间命令的执行需要保证原子性(使用lua脚本命令);
    2 当业务代码还没有执行完,锁就过期了的解决方法(使用定时器定时续约当前锁过期时间);

在这里插入图片描述

  • 下面对redisson中重要方法源码进行分析
    redisson分布式锁的执行源码分析记录一下_第3张图片
  • 在RedissonLock中有两个lock方法,一个是无参的,一个是有参的。他们的区别稍后在说,通过debug会进入到lock(long leaseTime, TimeUnit unit, boolean interruptibly)方法
    在这里插入图片描述
  • 这里获取了当前加锁的线程ID,在debug几次进入tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command)方法
    redisson分布式锁的执行源码分析记录一下_第4张图片
  • 这里一看就知道中间的一串lua脚本命令是重点,因为redis单线程执行的原因,使用lua脚本命令具有原子性,满足的分布式锁的条件之一。
    redisson分布式锁的执行源码分析记录一下_第5张图片
  • 根据redis.call(‘hincrby’, KEYS[1], ARGV[2], 1)命令可以得知,redisson对锁是以hash结构进行存储的。根据上面的三个参数代表:
    • KEYS[1]:redisson_key(相当于redis-key)
    • ARGV[1]:30000(key的过期时间)
    • ARGV[2]:92821d82-d541-4a1f-9f9e-ae908f0b9b38:1(相当于redis-hashKey)
  • 它的执行流程是:
    • 首先判断key是否存在。如果key不存在,则对key并且hashKey的hashValue赋值为1,并对其设置30秒的过期时间,最后返回null。
    • 如果key存在,再判断key和hashKey是否也存在。如果key和hashKey存在,则对其的hashValue值+1操作,并且对其设置30秒的过期时间,最后返回null。
    • 最后都不满足上面条件,那就返回key的剩余过期时间。
      redisson分布式锁的执行源码分析记录一下_第6张图片
  • 到这里加锁操作基本结束。使用工具看redis里面缓存的值。
    redisson分布式锁的执行源码分析记录一下_第7张图片
  • 继续debug会进入renewExpiration() 方法。在redisson源码设计里有个很重要的点是看门狗机制,这是为了防止锁还在使用的时候就过期了。源码里看门狗是个定时任务设计,定时时间是internalLockLeaseTime/3毫秒(30000/3 所以默认是10秒)。这里点击进入newTimeout()方法里发现这里定时器使用的是基于netty的HashedWheelTimer时间轮实现 netty中的定时机制HashedWheelTimer详解。
    redisson分布式锁的执行源码分析记录一下_第8张图片
  • renewExpirationAsync(long threadId) 方法是以lua脚本命令对锁进行续加过期时间:
    • 先判断key和hashkey是否存在,如果存在就设置为其重新设置30秒的过期时间,然后返回true。
    • 否则返回false。
      redisson分布式锁的执行源码分析记录一下_第9张图片
  • 最后如果加锁失败,那么ttl返回的是锁的剩余过期时间,那么就会进行上图中圈起来的subscribe(threadId)方法。
  • 到这里整个分布式加锁的两个主要流程解析完成。
    redisson分布式锁的执行源码分析记录一下_第10张图片
    ** 这里需要提一下的是如果不想使用redisson的看门狗机制,那么就使用带参数的加锁的lock(long leaseTime, TimeUnit unit)方法即可。传入leaseTime时间不为-1那就只会执行lua脚本加锁方法,不会执行后面的定时器方法。**
  • 之前看了加锁方法的方法的源码。继续debug到解锁方法里会进入unlockInnerAsync(long threadId)方法。
    redisson分布式锁的执行源码分析记录一下_第11张图片
    redisson分布式锁的执行源码分析记录一下_第12张图片
  • 上图参数解析:
    • KEYS[1]:redisson_key(相当于redis_key)
    • KEYS[2]:redisson_lock__channel:{redisson_key}(redis的channlName,一个分布式锁对应唯一的channelName)
    • ARGV[1]:0(publish发布通知的消息体)
    • ARGV[2]:30000(相当于key的过期时间)
    • ARGV[3]:92821d82-d541-4a1f-9f9e-ae908f0b9b38:1(相当于redis_hashKey)
  • 它的执行流程:
    • 首先判断key和hashKey如果不存在,返回null。
    • 存在就为其hashValue进行-1操作,并得到操作后的值。
    • 如果大于0,就为key重新赋值过期时间30秒。
    • 如果等于0,则删除key,并且发布通知。
  • 这里有两个比较重要的点:
    • 这里使用hash结构保存锁主要是为了重入锁实现。在前面加锁的lua命令中,如果根据key和hashkey找到数据后会对其hashValue进行+1操作,这里解锁lua命令就也有对找到的key和hashKey的hashValue进行-1操作。
    • 对于解锁lua脚本最后执行publish命令,是通知其他在加锁阶段而加锁失败进入subscribe(long threadId)方法的客户端线程可以进行加锁操作。
      redisson分布式锁的执行源码分析记录一下_第13张图片
  • 然后继续debug进入cancelExpirationRenewal(Long threadId)方法,这里会移除EXPIRATION_RENEWAL_MAP集合中的定时任务,从而结束看门狗定时器机制。
  • 到这里redisson解锁方法源码也基本解析完成。
  • 如果使用的是不带参数的lock()方法,说明有看门狗定时器机制守护执行线程,那么必须要调用unlock()解锁方法,否则看门狗定时器会一直给锁续加过期时间,只有程序一直执行那么锁就基本不会过期,相当于造成死锁。

我想结尾

  • 说实话redisson的源码相对于其他框架来说,是比较容易阅读和debug的。
  • 以上仅是个人浅薄的理解。如果文章中有什么纰漏或者错误的地方,请留言指出来,我会加以改正。
  • 虚心学习,共同进步 -_-

你可能感兴趣的:(redis,源码,redis,源码)