Redisson的看门狗watchDog机制是怎么实现的?

回顾下怎么加锁

RLock lock = redisson.getLock("myLock");
lock.lock();

lock干了啥

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
    long threadId = Thread.currentThread().getId();
    Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
    // 加锁成功
    if (ttl == null) {
        return;
    }
    // 加锁失败,while(true)等待重试。
}

可以看到 lock 主要是请求 tryAcquire (-1,-1, null , threadId )来完成加锁逻辑,然后判断加锁成功与否,失败的话就重试。目前还没发现 watchDog 的机制,那我们继续追下去,看看如何加锁的?

private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}

// watchDog机制在这里
// -1, -1, null,threadId
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    RFuture<Boolean> ttlRemainingFuture;
    // 省略一些请求lua加锁代码。之前都分析过。

    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
        // ttlRemaining为true代表加锁成功。
        if (ttlRemaining == null) {
      			// 接下来是什么鬼逻辑?
            // leaseTime == -1就scheduleExpirationRenewal开启看门狗续期,
            // leaseTime != -1就不续期,只是把internalLockLeaseTime时间变成传进来的时间。
            // 这里疑点重重:
            // 1.什么时候leaseTime != -1?
            // 2.不是所有的lock()方法都有看门狗机制?
            if (leaseTime != -1) {
                internalLockLeaseTime = unit.toMillis(leaseTime);
            } else {
                scheduleExpirationRenewal(threadId);
            }
        }
    });
    return ttlRemainingFuture;
}

意外收获!好像不是所有的 lock ()都有看门狗,因为看到条件判断 lease Time =-1的时候才开启看门狗线程,不等于﹣1的时候就没有这个
机制。那什么时候不等于﹣1呢?
回答这个问题前我们可以先推测下,-1是哪来的?是 lock ()入口默认带来的:

@Override
public void lock() {
    try {
        // -1 !!!
        lock(-1, null, false);
    } catch (InterruptedException e) {
        throw new IllegalStateException();
    }
}

带时间参数的lock方法:

@Override
public void lock(long leaseTime, TimeUnit unit) {
    try {
        lock(leaseTime, unit, false);
    } catch (InterruptedException e) {
        throw new IllegalStateException();
    }
}

时间传-1就会开启看门狗机制
现在知道 watchDog 何时生效了,那继续看下他是怎么工作的?
上文可以发现续期的代码在这个方法里面: scheduleExpirationRenewal ( threadId );这个方法底层是靠 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;
            }
			// 调用lua脚本进行续期
            RFuture<Boolean> future = renewExpirationAsync(threadId);
            future.onComplete((res, e) -> {
                // 报异常就移除key
                if (e != null) {
                    log.error("Can't update lock " + getRawName() + " expiration", e);
                    EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                    return;
                }
				// 续期成功的话就下一轮续期。
                if (res) {
                    // reschedule itself
                    renewExpiration();
                } else {
                    // 续期失败的话就取消续期,移除key等操作
                    cancelExpirationRenewal(null);
                }
            });
        }
        // 这里是个知识点,续期线程在过期时间达到三分之一的时候工作,比如9s过期时间,那么续期会在第3秒的时候工作,也就是还剩余6s的时候进行续期
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

    ee.setTimeout(task);
}

这里有四个关键点:

  • 续期核心 lua 脚本在 renewExpirationAsync 里
  • 续期成功自己调用自己,也就是为下一次续期做准备
  • 续期失败就取消续期,移除 key 等操作
  • 续期的开始时间是超过过期时间的三分之一,比如9s过期时间,那么第3s的时候开始续期。
    所以重点看下续期的 lua 源代码:
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
    return evalWriteAsync(getRawName(), 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(getRawName()),
            internalLockLeaseTime, getLockName(threadId));
}

很简单就是看当前线程有没有加锁 hexists , KEYS [1], ARGV [2])==1,有加锁的话就代表业务线程还没执行完,就给他的锁重新续期 pexpire ', KEYS [1], ARGV [1],然后返回1,也就是 true ,没加锁的话返回0,也就是 alse 。
那就是返回1就调用自己准备下一次续期: renewExpiration ();,返回0就调用 cancelExpirationRenewal ( null );取消续期,删除 key 等操作。
在这里插入图片描述

需要注意的点:

  • watchDog 并不是全部 lock 都生效,而是 lock 没设置过期时间的那些锁才会开启 watchDog 续期,没设置过期时间的话默认采取的是 watchDog 的30s过期时间。如果调用 lock ( time , unit )是不会开启 watchDog 线程续期的,是有可能造成线程不安全的。
  • 续期是段 lua 脚本。
  • 续期线程会在续期时间超过三分之一的时候执行。
    疑问:不会浪费性能吗?每个方法都起个看门狗线程,这个影响有多大?
    比篇讲述了看门狗是怎么工作的,他的核心原理我们一清二楚,整个加锁的这块流程算是告一段落了,接下来我们需要知道锁是怎么释放
    的?下篇分析!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值