使用redis分布式锁,自己写的或者是用Redisson,如果锁续期失败了怎么办?
我看了下Redisson实现的锁机制。Redisson会在获取锁时记录锁的leaseTime,然后在leaseTime到期前每隔一段时间(默认为30秒)向Redis发送续期指令,以延长锁的有效期。如果续期失败,Redisson会抛出RedissonLockTimeoutException异常,表示锁已经过期。
所以从系统健壮性考虑,我觉得有两个实现方式
1.捕获这个异常,再重新去获取锁
RLock lock = redisson.getLock("myLock");
try {
lock.lock();
// 执行任务
} catch (RedissonLockTimeoutException e) {
// 续约异常,重新获取锁
lock.unlock();
lock.lock();
// 执行任务
} finally {
lock.unlock();
}
2.捕获这个异常,告诉业务方这此操作异常,请重新操作,这个实现方式有甩锅嫌疑。或者我觉得你在异常捕获里面做异常业务处理,或者补全逻辑,比如发个消息告诉其他服务去异常处理。
try {
lock.lock();
// 执行任务
} catch (RedissonLockTimeoutException e) {
// 锁续约异常,
// todo 逻辑补全,兜底操作,业务报警等等操作,
//记录日志
log.error("获取锁超时,锁名称:{}", lock.getName(), e);
} finally {
lock.unlock();
}
当使用Redis实现分布式锁时,无论是自己编写的锁机制还是使用Redisson等库,处理锁续期失败的情况是很重要的。如果锁续期失败,可能导致其他客户端在预期之前获取到锁,从而破坏了分布式锁的正确性。以下是处理锁续期失败的一些常见方法:
设定合适的锁超时时间:在获取锁时,为锁设置适当的超时时间。超时时间应该足够长,以便在锁续期失败时有足够的时间进行处理,但又不应过长以避免锁长时间不释放。根据您的具体应用场景,权衡超时时间的设置是很重要的。
使用心跳机制进行锁续期:在锁的超时时间接近时,可以通过发送心跳来延长锁的有效期。心跳是指在锁的持有者周期性地发送一个更新锁信息的请求,以确保锁续期成功。其他客户端可以监视这些心跳并了解锁是否仍然有效。如果心跳停止或失败,可以视为锁续期失败,并进行相应的处理。
添加守护线程或定时任务:在锁的超时时间接近时,可以启动一个守护线程或定时任务来检查锁的状态并进行续期操作。这样可以确保锁始终保持有效,避免续期失败导致锁被其他客户端获取。
重试机制:如果锁续期失败,可以使用重试机制来尝试多次续期。在续期失败时,可以等待一段时间后再次尝试。您可以根据需要设置合适的重试间隔和重试次数。
定期检查锁的状态:除了续期机制外,您还可以定期检查锁的状态。例如,定期检查锁是否已过期或被其他客户端获取。如果检测到锁的状态异常,可以采取相应的处理措施。
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 {
RFuture<Boolean> future = RedissonLock.this.commandExecutor.evalWriteAsync(RedissonLock.this.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(RedissonLock.this.getName()), new Object[]{RedissonLock.this.internalLockLeaseTime, RedissonLock.this.getLockName(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 ((Boolean)future.getNow()) {
RedissonLock.this.scheduleExpirationRenewal(threadId);
}
}
}
});
}
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
if (expirationRenewalMap.putIfAbsent(this.getEntryName(), task) != null) {
task.cancel();
}
}
}