前面加锁的文章中提到了如果不传加锁时间,默认使用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() {
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) {
// reschedule itself
renewExpiration();
}
});
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
看门狗的一部分重点逻辑就在 renewExpiration
方法这里:
- 延迟调度,延迟时间为:internalLockLeaseTime / 3,就是 10s 左右后会调度这个 TimerTask;
- 异步续租:逻辑都在
renewExpirationAsync
里面; - 递归调用:当续租成功之后,重新调用 renewExpiration 自己,从而达到持续续租的目的;
- 当然也不能一直无限续租,所以中间有一些判断逻辑,就是用来中断续租的。
续租逻辑
1 | protected RFuture<Boolean> renewExpirationAsync(long threadId) { |
可以看到续租也是异步去执行一段 lua 脚本,当key和field都存在时,将redis中的key重新设置时间。 这样执行下来,将会在过了 10s 左右后将锁的时间重新设置为 30s。
总结
至此,看门狗介绍完毕,简要总结一下内容。
- 只有在未指定锁超时时间时才会使用看门狗;
- 看门狗默认续租时间是 10s 左右,internalLockLeaseTime / 3;
- 可以通过 Config 统一设置看门狗的时间,设置 lockWatchdogTimeout 参数即可。
最后,同样使用一张图,进行下总结: