分布式锁 - Redisson的看门狗(watchdog)机制
前言
本篇文章从Redisson的加锁(tryLock)入手,带大家由源码来了解一下watchdog的自动延迟加锁操作,如果对Redisson的加锁机制没有了解,建议可以先看一下本人的另一篇博客分布式锁-Redisson的使用及源码分析
结论
- 想要触发Redisson看门狗机制,不能自定义 leaseTime(或者传参 -1)
- Redisson默认加锁30秒,每隔10秒刷新加锁时间
- watchdog的延时时间 可以由 lockWatchdogTimeout指定默认延时时间,但是不要设置太小
- Redisson是通过Future和Timeout功能来实现异步延时
源码分析
前文讲到 tryLock() 中最重要的加锁逻辑是 tryAcquire(waitTime, leaseTime, unit, threadId),今天我们就来从加锁的方法入手看一下如何完成WatchDog
Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
接下来,我们进入这个方法看一下
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
// get() 方法异步阻塞式获取加锁结果
return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}
/**
* 重点方法 看这里
*/
private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
RFuture<Boolean> acquiredFuture;
// 1、判断锁的持有时间是否由用户自定义(这里我不在细节讨论,感兴趣的童鞋们可以去看上一篇文章)
if (leaseTime != -1) {
acquiredFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
} else {
// 2、当用户没有自定义锁占有时间时,默认传入 internalLockLeaseTime
// private long lockWatchdogTimeout = 30 * 1000; 默认30秒
acquiredFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
}
CompletionStage<Boolean> f = acquiredFuture.thenApply(acquired -> {
// lock acquired
if (acquired) {
if (leaseTime != -1) {
// 3、如果用户传入占用时间直接转换,把默认值internalLockLeaseTime 更新为用户自定义的占有时间
internalLockLeaseTime = unit.toMillis(leaseTime);
} else {
// 4、看这里,看这里,没错,这里就是触发看门狗机制的方法
// 重点:只有当 leaseTime == -1时才会触发看门狗机制
scheduleExpirationRenewal(threadId);
}
}
return acquired;
});
return new CompletableFutureWrapper<>(f);
}
我们来看一下方法:scheduleExpirationRenewal(threadId);
protected void scheduleExpirationRenewal(long threadId) {
ExpirationEntry entry = new ExpirationEntry();
// 1、EXPIRATION_RENEWAL_MAP 是一个全局的静态常量Map
ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
if (oldEntry != null) {
// 2、oldEntry != null 说明该线程不是第一次触发
oldEntry.addThreadId(threadId);
} else {
// 3、oldEntry == null 说明该线程是第一次触发
entry.addThreadId(threadId);
try {
// 4、更新过期时间
renewExpiration();
} finally {
if (Thread.currentThread().isInterrupted()) {
cancelExpirationRenewal(threadId);
}
}
}
}
接下来,我们一起看一下renewExpiration()是如何更新过期时间的
private void renewExpiration() {
// 1、获取当前线程的更新对象
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee == null) {
return;
}
// 2、创建了一个定时任务
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;
}
// 3、异步更新过期时间
RFuture<Boolean> future = renewExpirationAsync(threadId);
// 4、res 执行结果,e 异常
future.whenComplete((res, e) -> {
if (e != null) {
// 5、如果出现异常,从map中删除,直接返回
log.error("Can't update lock " + getRawName() + " expiration", e);
EXPIRATION_RENEWAL_MAP.remove(getEntryName());
return;
}
if (res) {
// 6、如果没有报错,就再次定时延期
renewExpiration();
} else {
// 7、否则取消定时
cancelExpirationRenewal(null);
}
});
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
// 8、延迟时间为:internalLockLeaseTime / 3 也就是10秒
ee.setTimeout(task);
}
// 通过lua脚本判断给key重新设置过期时间
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));
}

本文深入探讨了Redisson的分布式锁实现,特别是其看门狗(watchdog)机制。当未自定义锁的持有时间时,Redisson会默认加锁30秒,并每10秒自动续期。通过`scheduleExpirationRenewal`方法触发看门狗,使用`renewExpirationAsync`异步更新锁的过期时间。该机制确保了锁的有效性和可靠性。
机制&spm=1001.2101.3001.5002&articleId=128407627&d=1&t=3&u=69050c4ac1ee481083be231d95a7b83d)
2万+

被折叠的 条评论
为什么被折叠?



