
在上篇中,我们梳理了redisson的可重入锁的加锁流程,而加锁必然就会有锁续期的问题,那么看门狗机制是维持锁续期的关键。因此,在本篇中我们将剖析redisson中的看门狗机制究竟是如何实现的。
看门狗机制是redisson解决锁续期问题而设置的,在前文中我们也有手写过,这里我们看看“正版”的是如何执行的。
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
Long ttl = null;
// 如果未指定租约时间,则使用看门狗机制
if (leaseTime != -1) {
ttl = tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
// 使用默认的看门狗超时时间(默认30秒)
ttl = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
// 启动看门狗
scheduleExpirationRenewal(threadId);
}
return ttl;
}private 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() {
// 创建异步续期任务
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee == null) {
return;
}
// 通过Lua脚本延长锁的过期时间
RFuture<Boolean> future = renewExpirationAsync(threadId);
future.onComplete((success, e) -> {
if (e != null) {
log.error("Can't update lock " + getName() + " expiration", e);
return;
}
if (success) {
// 如果续期成功,递归调用,实现循环续期
renewExpiration();
}
});
}
// 续期间隔为看门狗超时时间的1/3
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}protected RFuture<Boolean> renewExpirationAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), 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(getName()),
internalLockLeaseTime, // 默认30秒
getLockName(threadId));
}void cancelExpirationRenewal(Long threadId) {
ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (task == null) {
return;
}
if (threadId != null) {
task.removeThreadId(threadId);
}
if (threadId == null || task.hasNoThreads()) {
// 取消定时任务
task.getTimeout().cancel();
EXPIRATION_RENEWAL_MAP.remove(getEntryName());
}
}让我们总结一下看门狗机制的工作流程:
另外,通过学习源码,我们要正确使用看门狗机制,我们需要注意:不要使用带超时参数的加锁方法,否则看门狗机制不会生效;确保业务执行时间不会过长,否则会产生大量续期操作;记得在完成业务后及时释放锁,避免资源浪费。
看门狗机制的关键在于定时对锁的状态进行检查,回想下我们当时手撸是通过循环进行的,这里显然高明多了,采用的定时任务,并且定时任务的执行频率也是过期时间的1/3,执行效率也比我们采用循环的方式高得多。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。