Redisson分布式锁,重试锁和锁续命的原理

RedissonLock 锁重试原理

tryLock有三个三个参数,第一个是等待时间,第二个是锁失效后自动释放的时间,不填默认为-1,第三个是时间单位;

当设置了第一个参数,那这个锁就成了可重试锁;获取锁失败后,就不会立即返回了;会在等待内不断重试;如果在等待时间结束后,还没有获取到锁,那就失败了。

获取当前时间,线程ID,尝试获取锁,判断锁失效后自动释放的时间是否等于-1,如果不等于,就用自己的锁释放时间,如果等于-1,异步调用tryLockInnerAsync,返回值是个Future,第一个参数是等待时间,第二个参数是锁释放时间,看门狗的默认30秒,第三个是时间单位,第四个参数是线程ID,这个方法内是个lua脚本,成功返回空,是否返回过期时间;判断返回值是否为空,不为空,计算剩余等待时间,判断等待时间>0,大于就去尝试获取锁,但不是立即获取锁,是在剩余等待时间内订阅了锁释放的情况(锁释放的时候会发布通知),返回值也是个Future,如果超时了,会取消订阅;如果锁已经释放了,计算剩余等待时间,判断剩余等待时间>0,开始循环,就可以重新获取锁,和上面一样,如果失败,同时上,但这里用的是信号量。


lock.tryLock方法

第一个参数(等待时间),如果设置了,获取锁失败后,就不会立即返回了;会在等待内不断重试;如果在等待时间结束后,还没有获取到锁,那就失败了。所以设置后就变成了可重试的锁了。
第二个参数(锁失效后自动释放的时间,不填默认为-1)
第三个参数(时间单位)
源码跟进

第二个参数没给,默认值-1,跟进去tryLock方法看看


跟进tryAcquire方法,尝试获取锁

跟进tryAcquireAsync方法

java 复制代码
//源码
  private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {	
 		 //判断释放锁的时间是否为-1
        if (leaseTime != -1L) {
       		 //用自己的释放锁的时间
            return this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
        	//如果是-1,设置一个默认的释放锁的时间,30秒(getLockWatchdogTimeout)
            RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
            ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                if (e == null) {
                    if (ttlRemaining == null) {
                        this.scheduleExpirationRenewal(threadId);
                    }

                }
            });
            return ttlRemainingFuture;
        }
    }


跟进getLockWatchdogTimeout看门狗

显示默认30秒

返回RedissonLock类,继续跟进tryLockInnerAsync方法

tryLockInnerAsync 异步方法,有没有拿到结果不清楚

看到有lua脚本

lua脚本

判断锁是否存在

不存在设置锁标识,v+1

设置有效期

存在,判断锁是不是我自己的

是,v+1

设置有效期

成功获取锁,返回空

失败,返回锁的有效期(毫秒)

java 复制代码
  <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
  		//释放锁的时间,记录到本地一个成员变量里
        this.internalLockLeaseTime = unit.toMillis(leaseTime);
        return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, 
        	"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]);", 
        Collections.singletonList(this.getName()), this.internalLockLeaseTime, this.getLockName(threadId));
    }

一步一步往上返回,返回到RedissonLock类中的tryLock方法的tryAcquire方法,结果有两种,一种是获取锁成功返回值为空,另一种是获取锁失败返回值为锁释放的时间


继续

锁续命

当用默认的锁释放时间时,且已经拿到锁,会创建一个 ConcurrentMap来存锁的记录,以锁的名称为k,线程id为V,判断是否第一次来,如果是开启一个延时任务,10秒后执行,执行的就是更新有效期的lua脚本,更新成功后,开始递归,无限续约。

先看锁释放时间是默认值-1的情况

跟进 scheduleExpirationRenewal 方法

跟进 renewExpiration 方法

跟进 renewExpirationAsync 方法

判断锁是不是当前线程拿到的,是就重置有效期

java 复制代码
   protected RFuture<Boolean> renewExpirationAsync(long threadId) {
        return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, 
        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1)
         then redis.call('pexpire', KEYS[1], ARGV[1]); 
         return 1; 
         end; 
         return 0;",
          Collections.singletonList(this.getName()), this.internalLockLeaseTime, this.getLockName(threadId));
    }

执行lua脚本后

释放锁


相关推荐
zmd-zk1 分钟前
kafka+zookeeper的搭建
大数据·分布式·zookeeper·中间件·kafka
yx9o6 小时前
Kafka 源码 KRaft 模式本地运行
分布式·kafka
Gemini19956 小时前
分布式和微服务的区别
分布式·微服务·架构
G丶AEOM6 小时前
分布式——BASE理论
java·分布式·八股
P.H. Infinity12 小时前
【RabbitMQ】03-交换机
分布式·rabbitmq
龙哥·三年风水14 小时前
群控系统服务端开发模式-应用开发-个人资料
分布式·php·群控系统
funnyZpC16 小时前
quartz集群增强版🎉
java·分布式·开源·集群·定时任务
明达技术17 小时前
工业4.0时代下的分布式IO模块
分布式
天冬忘忧18 小时前
Spark 程序开发与提交:本地与集群模式全解析
大数据·分布式·spark
一叶飘零_sweeeet20 小时前
Dubbo 构建高效分布式服务架构
分布式·架构·dubbo