前言
本篇主要介绍基于Redisson实现的分布式锁,获取锁之后,通过watchdog机制异步的,定时的,递归的判断是否存活,从而进行锁续期
Watchdog机制
在使用Redisson分布式的锁的过程中,如果客户端的请求线程获取锁之后,由于当前任务执行时间较长,线程任务没执行完毕,但又超过了线程占有这把锁的时间(初始时间是30s)
那么就需要watchdog机制对当前线程锁持有得到这把锁进行续期,从而保证任务执行完毕
源码分析
通过以下代码,可以看到,当前线程执行lock方法时,会执行tryAcquireAsync方法,在该方法中,执行lua脚本判断加锁是否存在,并且给到锁对应的有效期,异步返回Future对象
同时,当Future对象获取到值,且线程已经获取到锁之后(怎么判断当前线程已经获取到锁,见备注),scheduleExpirationRenewal()方法 会执行看门狗锁续期的逻辑
注意:
1.这里源码版本是3.6,获取Future对象的值方式可能有区别,原有的版本使用的监听器的方式,这里使用的是JDK8的并发的API
2.如果当前线程一直持有锁没有释放,则后台的watchdog线程,默认每隔10s会将持有锁的线程的锁的有效期续期至默认的30s;
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
//返回当前加锁的Key的剩余时间的Future对象
RFuture<Long> ttlRemainingFuture;
if (leaseTime != -1) {
//自定义的锁的占有时间
ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
//使用默认的锁占有时间
ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
}
//这里当Future对象获取到异步线程的执行结果时,调用下面方法执行,注意,这里watchdog续期的使用的也是异步线程
CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {
// lock acquired
if (ttlRemaining == null) {
//这里为什么ttlRemaining为null表示当前线程获取到锁,是因为上述tryLockInnerAsync中执行的lua脚本判断锁对象对应的Key已经不存在或已过期
if (leaseTime != -1) {
//自定义的锁的占有时间
internalLockLeaseTime = unit.toMillis(leaseTime);
} else {
//默认情况下会,会执行锁续期的逻辑
scheduleExpirationRenewal(threadId);
}
}
return ttlRemaining;
});
return new CompletableFutureWrapper<>(f);
}
scheduleExpirationRenewal 续期的逻辑如下
进一步调用 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.whenComplete((res, e) -> {
if (e != null) {
log.error("Can't update lock " + getRawName() + " expiration", e);
EXPIRATION_RENEWAL_MAP.remove(getEntryName());
return;
}
if (res) {
// reschedule itself
//递归调用锁续期的方法,直到线程任务执行结束(也就是在Redisson维护的Map中找不到对应的Key)
renewExpiration();
} else {
//取消锁续期的逻辑
cancelExpirationRenewal(null);
}
});
}
//这里是通过续期时间/3,例如,30s续期时间,则每隔10s执行一次续期的逻辑,将锁的存活时间延长至 internalLockLeaseTime默认的30s,
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
锁续期的lua脚本
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
//判断加锁key的值Map中,当前线程对应值是否为1,如果为1,表示当前线程还是持有锁的,持有锁,就要进行续期
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
//将加锁key对应的Map中,对应加锁线程的值重新设置为 internalLockLeaseTime 30秒
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return 1; " +
"end; " +
"return 0;",
Collections.singletonList(getRawName()),
internalLockLeaseTime, getLockName(threadId));
}