前言

  本篇主要介绍基于Redisson实现的分布式锁,获取锁之后,通过watchdog机制异步的,定时的,递归的判断是否存活,从而进行锁续期

Watchdog机制

  在使用Redisson分布式的锁的过程中,如果客户端的请求线程获取锁之后,由于当前任务执行时间较长,线程任务没执行完毕,但又超过了线程占有这把锁的时间(初始时间是30s)

那么就需要watchdog机制对当前线程锁持有得到这把锁进行续期,从而保证任务执行完毕

源码分析

  通过以下代码,可以看到,当前线程执行lock方法时,会执行tryAcquireAsync方法,在该方法中,执行lua脚本判断加锁是否存在,并且给到锁对应的有效期,异步返回Future对象

  同时,当Future对象获取到值,且线程已经获取到锁之后(怎么判断当前线程已经获取到锁,见备注),scheduleExpirationRenewal()方法 会执行看门狗锁续期的逻辑

  注意:

    1.这里源码版本是3.6,获取Future对象的值方式可能有区别,原有的版本使用的监听器的方式,这里使用的是JDK8的并发的API

    2.如果当前线程一直持有锁没有释放,则后台的watchdog线程,默认每隔10s会将持有锁的线程的锁的有效期续期至默认的30s;

  

private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        //返回当前加锁的Key的剩余时间的Future对象
        RFuture<Long> ttlRemainingFuture;
        if (leaseTime != -1) {
            //自定义的锁的占有时间
            ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
            //使用默认的锁占有时间
            ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                    TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        }
        //这里当Future对象获取到异步线程的执行结果时,调用下面方法执行,注意,这里watchdog续期的使用的也是异步线程
        CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {
            // lock acquired
            if (ttlRemaining == null) {
                //这里为什么ttlRemaining为null表示当前线程获取到锁,是因为上述tryLockInnerAsync中执行的lua脚本判断锁对象对应的Key已经不存在或已过期
                if (leaseTime != -1) {
                    //自定义的锁的占有时间
                    internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
                    //默认情况下会,会执行锁续期的逻辑
                    scheduleExpirationRenewal(threadId);
                }
            }
            return ttlRemaining;
        });
        return new CompletableFutureWrapper<>(f);
    }

 

   scheduleExpirationRenewal 续期的逻辑如下

   进一步调用 renewExpiration()方法

private void renewExpiration() {
        ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (ee == null) {
            return;
        }
        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
                if (ent == null) {
                    return;
                }
                Long threadId = ent.getFirstThreadId();
                if (threadId == null) {
                    return;
                }
                
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.whenComplete((res, e) -> {
                    if (e != null) {
                        log.error("Can't update lock " + getRawName() + " expiration", e);
                        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                        return;
                    }
                    
                    if (res) {
                        // reschedule itself
                        //递归调用锁续期的方法,直到线程任务执行结束(也就是在Redisson维护的Map中找不到对应的Key)
                        renewExpiration();
                    } else {
                        //取消锁续期的逻辑
                        cancelExpirationRenewal(null);
                    }
                });
            }
            //这里是通过续期时间/3,例如,30s续期时间,则每隔10s执行一次续期的逻辑,将锁的存活时间延长至 internalLockLeaseTime默认的30s,
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        ee.setTimeout(task);
    }

   锁续期的lua脚本

protected RFuture<Boolean> renewExpirationAsync(long threadId) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                //判断加锁key的值Map中,当前线程对应值是否为1,如果为1,表示当前线程还是持有锁的,持有锁,就要进行续期
                "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                        //将加锁key对应的Map中,对应加锁线程的值重新设置为 internalLockLeaseTime 30秒
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return 1; " +
                        "end; " +
                        "return 0;",
                Collections.singletonList(getRawName()),
                internalLockLeaseTime, getLockName(threadId));
    }