Redisson是怎么实现可重试锁的

Redisson是怎么实现可重试锁的

可重试锁是什么:一个请求尝试获取锁时,如果锁已被人占用,则立即返回失败结束请求是不合理的。最好的方式是给予一定的容错空间,在一定的时间内再多次尝试获取锁,如果长时间仍获取不到才返回失败。而简单的基于redis实现的分布式锁不能实现该功能。redisson可以实现该功能。

逻辑原理:

上锁的时候会设定一个等待超时的时间,例如3s;第一次获取锁失败,会判断是否超过3s可等待时间,如果未超过,也不能一直不间断的重试,这样只会徒劳占用资源,而是应订阅释放锁的信号,如果在等待时间范围内订阅到信号,则再去尝试获取锁。超过该时间仍未获取则返回失败。

逻辑图如下:

在这里插入图片描述

下面我们进入redisson上锁源码:

可以看到调用tryLock()方法时,第一个可传参就是等待时间
在这里插入图片描述

进入tryLock(long time, TimeUnit unit)方法,选择RedissonLock实现类,最后会调用this.tryLock(waitTime, -1L, unit)方法,进入方法:

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
    long time = unit.toMillis(waitTime);
    //【获取当前时间】
    long current = System.currentTimeMillis();
    long threadId = Thread.currentThread().getId();
    //【1 尝试获取锁】
    Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
    // lock acquired
    if (ttl == null) {
        //获取锁成功
        return true;
    }
    //获取锁失败,计算当前剩余的等待时间
    time -= System.currentTimeMillis() - current;
    if (time <= 0) {
        //如果已经超时,则返回失败
        acquireFailed(waitTime, unit, threadId);
        return false;
    }
    //还未超时
    current = System.currentTimeMillis();
    //【2 订阅释放锁的信号。在释放锁的时候会再删除锁之后发布一条通知】
    CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
    try {
        subscribeFuture.get(time, TimeUnit.MILLISECONDS);
    } catch (TimeoutException e) {
        if (!subscribeFuture.completeExceptionally(new RedisTimeoutException(
                "Unable to acquire subscription lock after " + time + "ms. " +
                        "Try to increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."))) {
            subscribeFuture.whenComplete((res, ex) -> {
                if (ex == null) {
                    unsubscribe(res, threadId);
                }
            });
        }
        acquireFailed(waitTime, unit, threadId);
        return false;
    } catch (ExecutionException e) {
        acquireFailed(waitTime, unit, threadId);
        return false;
    }
    //【重试阶段】
    try {
        time -= System.currentTimeMillis() - current;
        if (time <= 0) {
            //若此时已经超时,则失败
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
    	//【3 循环重复尝试】
        while (true) {
            long currentTime = System.currentTimeMillis();
            //尝试获取锁
            ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
            // lock acquired
            if (ttl == null) {
                //获取锁成功
                return true;
            }
            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }
            // waiting for message
            currentTime = System.currentTimeMillis();
            //返回的ttl是缓存中被人占用的key剩余的过期时间
            if (ttl >= 0 && ttl < time) {
                //【4 等待获取信号量】
                //如果剩余释放时间只剩2s,等待时间还有3秒,则只需要等待2s即可
                commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
            } else {
                //等待time时间
                commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
            }
            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }
            //如果这时候还有时间则在进入循环
        }
    } finally {
        //取消订阅
        unsubscribe(commandExecutor.getNow(subscribeFuture), threadId);
    }
}

​ 可以看到,程序会先尝试获取锁见步骤【1】,如果获取失败就要准备循环尝试再次获取。但也不能一直不间断的循环,那样只会空耗资源。所以见步骤【2】,订阅释放锁的消息(在解锁的时候会发布这个订阅)。订阅完成后进入步骤【3】再次尝试获取,如果失败,进入步骤【4】等待获取发布的订阅信息。最长的等待时间不会超过传入的等待时间time。如果接收到订阅信号,且此时还没超时,就再循环尝试获取锁,也有可能别的请求速度更快抢占了锁,则只能再次进入等待方法;如果超时则退出。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猪大侠0.0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值