背景
String lockKey = "forlan_lock_" + serviceId;
RLock lock = redissonClient.getLock(lockKey;
// 方式1
try {
lock.lock(5, TimeUnit.SECONDS;
// 执行业务
...
} catch (Exception e {
e.printStackTrace(;
} finally {
// 释放锁
lock.unlock(;
}
// 方式2
try {
if (lock.tryLock(5, 5, TimeUnit.SECONDS {
// 获得锁执行业务
...
}
} catch (Exception e {
e.printStackTrace(;
} finally {
// 释放锁
lock.unlock(;
}
分析
像上面的写法,符合我们的常规思维,一般,为了避免程序挂了的情况,没有释放锁,都会设置一个过期时间
但这个过期时间,一般设置多长?
设置过长,如果程序挂了,需要等待比较长的时间,锁才释放,占用资源
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId {
if (leaseTime != -1L {
// 前面我们指定了过期时间,会进到这里,直接加锁
return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG;
} else {
// 没有指定过期时间的话,默认采用LockWatchdogTimeout,默认是30s
RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager(.getCfg(.getLockWatchdogTimeout(, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG;
// ttlRemainingFuture执行完,添加一个监听器,类似netty的时间轮
ttlRemainingFuture.addListener(new FutureListener<Long>( {
public void operationComplete(Future<Long> future throws Exception {
if (future.isSuccess( {
Long ttlRemaining = (Longfuture.getNow(;
if (ttlRemaining == null {
RedissonLock.this.scheduleExpirationRenewal(threadId;
}
}
}
};
return ttlRemainingFuture;
}
scheduleExpirationRenewal方法
private void scheduleExpirationRenewal(final long threadId {
if (!expirationRenewalMap.containsKey(this.getEntryName( {
Timeout task = this.commandExecutor.getConnectionManager(.newTimeout(new TimerTask( {
public void run(Timeout timeout throws Exception {
// renewExpirationAsync就是执行续期的方法
RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId;
// 什么时候触发执行?
future.addListener(new FutureListener<Boolean>( {
public void operationComplete(Future<Boolean> future throws Exception {
RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName(;
if (!future.isSuccess( {
RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName( + " expiration", future.cause(;
} else {
if ((Booleanfuture.getNow( {
RedissonLock.this.scheduleExpirationRenewal(threadId;
}
}
}
};
}
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS; // 当跑了LockWatchdogTimeout的1/3时间就会去执行续期
if (expirationRenewalMap.putIfAbsent(this.getEntryName(, new RedissonLock.ExpirationEntry(threadId, task != null {
task.cancel(;
}
}
所以,结论是啥?
// 方式1
lock.lock(5, TimeUnit.SECONDS;
// 方式2
lock.tryLock(5, 5, TimeUnit.SECONDS
我们这两种写法都会导致看门狗机制失效,如果业务执行超过5s,就会出问题
解决
// 方式1
lock.lock(;
// 方式2
lock.tryLock(5, -1, TimeUnit.SECONDS
你可以会觉得不妥,不指定的话,就默认按照30s续期时间,然后每10s去看看有没有执行完,没有就续期,
我们也可以指定续期时间,比如指定为15s
config.setLockWatchdogTimeout(15000L;
总结
- 在使用Redisson实现分布式锁,不应该设置过期时间
- 看门狗默认续期时间是30s,可以通过setLockWatchdogTimeout指定
- 看门狗会每internalLockLeaseTime / 3L去续期
- 看门狗底层实际就是类似Netty的时间轮