Redisson分布式锁源码浅析

前面分析了用redis实现一个分布式锁需要注意的问题,并用redis手写了一个简易的分布式锁,但在实际开发中更多的是使用已经封装好的框架——Redisson,它是在redis基础之上封装的一款强大的分布式锁框架,它是多种锁的集合,包括可重入锁、公平锁、信号量等,当我们在分布式场景下不能使用本地锁时,只需要去Redisson调对应的锁即可。本篇就以可重入锁为例来浅析Redisson是如何实现分布式锁的。

首先来用Redisson优化上一章中结尾处的代码,如下:
    @Resource
    private RedissonClient redissonClient;
 
    @Override
    public List<Map<String, Object>> findListByRedisson() {
        List<Map<String, Object>> resultList = new ArrayList<>();
        //获取可重入锁
        RLock lock = redissonClient.getLock("lock");
        //加锁,阻塞式等待
        lock.lock();
        try{
            resultList = findListByDB();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            //解锁
            lock.unlock();
        }
        return resultList;
    }

是不是觉得这和我们平时调用本地锁没什么区别呢,没错Redisson就是这么朴实无华,代码简洁不说,内部还帮我们解决了上章中遗留的“锁续命”问题,下面就正式进入源码浅析!

  首先通过Redisson客户端获取一把可重入锁,接着调用这个锁的lock()方法尝试加锁,进入lock()方法,如下,可以看到该方法其实还可以传入一个时间作为参数,这个时间代表锁的超时时间,到了这个时间锁就会被释放掉,如果我们没有传入超时时间,那么这个参数会被置为-1。

在这里插入图片描述
继续进入内部的lock()方法,里面主要是调用tryAcquire()方法尝试加锁,如果返回null则代表加锁成功返回,否则在后面用while循环继续尝试获取锁。 【tryAcquire()中的三个参数waitTime(就是重试等待时间),leaseTime(过期时间),TimeUnit(时间单位)】
在这里插入图片描述

进入tryAcquire()→ tryAcquireAsync()方法,该方法首先会判断释放时间是否为-1,如果为-1则代表我们在前面没有传入超时时间,根据这个判断结果将会走两条不同的路。

    1、第一条路:首先来看如果我们在前面传入了自定义超时时间,那么将会执行tryLockInnerAsync()方法,可以看到里面是很直接的在和redis通信,将一段lua脚本以及自定义的超时时间等信息传给redis进行占锁。需注意的是这个方法的返回值是Future,这里用到了异步编排,如果占锁成功,会在后面用这个返回值进行监听。

LUA脚本实现锁重入逻辑。KEYS[1]:锁名称,ARGV[1]:锁失效时间,ARGV[2]:线程标识

if (redis.call('exists', KEYS[1]) == 0) 
then redis.call('hincrby', KEYS[1], ARGV[2], 1); 
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil; 
end; 
 if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil; 
end;
 return redis.call('pttl', KEYS[1]);

在这里插入图片描述
2、第二条路:如果没有传入超时时间,那么不仅会调用tryLockInnerAsync()方法,还会通过一个scheduleExpirationRenawal()的方法进行锁续期。
在这里插入图片描述
来看一下tryLockInnerAsync()方法在这里传入的参数,当前面没有传入超时时间时,这里传入了一个“internalLockLeaseTime”参数来替代,追踪这个参数值,可以看到在RedissonLock构造时为这个参数赋了值,而这个时间值就是配置中的“看门狗”的时间,这个值为30s。

在这里插入图片描述

在这里插入图片描述
也就是说,即使我们不传入超时时间,Redisson也会自动给我们设置一个默认的超时时间30s。

    下面再来看神秘的scheduleExpirationRenawal()方法,这是在占锁成功后进行监听过程中涉及到的方法,须注意的是如果我们传入了自定义超时时间是不会执行该方法的。再看这个方法里面有一个重要的方法——renewExpiration(),看名字也能猜出来这是用于重新设置超时时间。进入这个方法,看核心部分可以知道主要是通过一个连接管理器创建了一个定时任务,任务的内容就是用lua脚本和redis通信重新设置超时时间,这个过期时间依然是看门狗的超时时间30s。

在这里插入图片描述
在这里插入图片描述
那么问题来了,什么时候执行这个任务去重新设置超时时间呢?继续看刚才创建定时任务部分的代码,可以知道是看门狗时间的三分之一即10s执行一次续期,每次重新设置过期时间为30s。

在这里插入图片描述
总结:在调用lock()方法时,我们可以传入自定义的超时时间,也可以不传,如果不传那么将会使用Redisson默认的看门狗时间30s,需要注意的是,如果我们使用了自定义超时时间,那么Redisson不会自动为我们的锁续期,而不传时间Redisson会使用默认30s超时时间并且会自动为我们的锁进行续期。而一般在开发中,最佳实践其实推荐的是传入超时时间,因为这样省去了创建定时任务不断续时的过程,可能会有小伙伴会问那如果业务时间大于了超时时间怎么办,其实只需要把超时时间设的大一点就可以了,比如就30s,如果超过30s业务还没完成就该想想怎么优化业务逻辑了。。。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/h2503652646/article/details/119514951
参考链接 :https://blog.csdn.net/m0_64116616/article/details/138925028

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值