前面加锁的文章中提到了如果不传加锁时间,默认使用30s,而并不是永久时长,拿到了30s后难道锁就会自动释放吗?答案是不会,因为Redisson中还有一个耳熟能详的看门狗(Watchdog)机制。
本文就一起看看加锁成功之后的看门狗(Watchdog)是如何实现的?
加锁成功
在前一篇文章中介绍了可重入锁加锁的逻辑,其中 RedissonLock 类的 tryAcquireAsync 方法是进行异步加锁的逻辑。
回顾一下调用lock()方法时tryAcquireAsync这个方法的入参:
- waitTime:-1;
- leaseTime:-1,加锁时未指定锁时间,则为 -1,如果指定,则是指定的时间;
- unit:null;
- threadId:当前线程 id。
其中的 tryLockInnerAsync 在前面那篇文章已经介绍过了。当加锁成功时,该会返回 null,加锁失败,会返回当前锁的剩余时间。
所以如果加锁成功,就会进入到红框标记的代码部分。

因为 leaseTime 的值为-1,所以会进入到 scheduleExpirationRenewal 方法,也就是这篇文章的主题:看门狗。
此外根据 leaseTime 的判断,还可以得出一个结论: Redisson 看门狗(Watchdog)在指定加锁时间时,是不会对锁时间自动续租的。只有不指定加锁时间才会续租。
看门狗
scheduleExpirationRenewal方法位于RedissonLock的父类RedissonBaseLock类中,并且最终会调用到该类中的renewExpiration()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| protected void scheduleExpirationRenewal(long threadId) { ExpirationEntry entry = new ExpirationEntry(); ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry); if (oldEntry != null) { oldEntry.addThreadId(threadId); } else { entry.addThreadId(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; } RFuture<Boolean> future = renewExpirationAsync(threadId); future.onComplete((res, e) -> { if (e != null) { log.error("Can't update lock " + getRawName() + " expiration", e); EXPIRATION_RENEWAL_MAP.remove(getEntryName()); return; } if (res) { renewExpiration(); } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); ee.setTimeout(task); }
|
看门狗的一部分重点逻辑就在 renewExpiration 方法这里:
- 延迟调度,延迟时间为:internalLockLeaseTime / 3,就是 10s 左右后会调度这个 TimerTask;
- 异步续租:逻辑都在
renewExpirationAsync 里面;
- 递归调用:当续租成功之后,重新调用 renewExpiration 自己,从而达到持续续租的目的;
- 当然也不能一直无限续租,所以中间有一些判断逻辑,就是用来中断续租的。
续租逻辑
1 2 3 4 5 6 7 8 9 10
| 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)); }
|
可以看到续租也是异步去执行一段 lua 脚本,当key和field都存在时,将redis中的key重新设置时间。 这样执行下来,将会在过了 10s 左右后将锁的时间重新设置为 30s。
总结
至此,看门狗介绍完毕,简要总结一下内容。
- 只有在未指定锁超时时间时才会使用看门狗;
- 看门狗默认续租时间是 10s 左右,internalLockLeaseTime / 3;
- 可以通过 Config 统一设置看门狗的时间,设置 lockWatchdogTimeout 参数即可。
最后,同样使用一张图,进行下总结:
