《Redisson 分布式锁自动续期机制解析》
Redisson 的分布式锁自动续期机制(看门狗原理)是其核心特性之一,用于防止业务执行时间过长导致锁提前过期,从而引发并发安全问题。以下是其工作原理的详细解析:
一、自动续期的触发条件
当使用 lock()
方法获取锁且未指定锁的过期时间时,Redisson 会自动启动看门狗机制:
csharp
RLock lock = redisson.getLock("myLock");
lock.lock(); // 未指定过期时间,触发看门狗
// 等价于 lock.lock(-1, null);
而显式指定过期时间的方法(如 lock(10, TimeUnit.SECONDS)
)不会触发看门狗,锁会在指定时间后自动释放。
二、看门狗的核心参数
-
LockWatchdogTimeout(默认 30 秒):看门狗的检查周期,即锁的默认持有时间。
-
检查频率 :每
LockWatchdogTimeout/3
(默认 10 秒)检查一次,若锁仍被持有,则延长锁的有效期。
三、自动续期的实现原理
- 初始加锁:
-
当调用
lock()
时,Redisson 会通过 Lua 脚本向 Redis 发送加锁命令,设置锁的初始过期时间为LockWatchdogTimeout
(默认 30 秒)。 -
锁的数据结构为
Hash
,键为锁名称,field
为线程 ID,value
为重入次数。
- 启动定时任务:
- 加锁成功后,Redisson 会启动一个后台定时任务 (通过 Netty 的
HashedWheelTimer
实现),每隔 10 秒执行一次。
- 续期逻辑:
-
定时任务通过 Lua 脚本检查锁是否存在且被当前线程持有。
-
若条件满足,则通过
pexpire
命令将锁的过期时间重置为LockWatchdogTimeout
(30 秒)。 -
续期成功后,继续等待下一个检查周期。
- 锁释放时终止续期:
- 当调用
unlock()
释放锁时,会同时取消续期的定时任务,避免无限续期。
四、续期的 Lua 脚本示例
ini
\-- KEYS\[1]: 锁名称
\-- ARGV\[1]: 过期时间(LockWatchdogTimeout)
\-- ARGV\[2]: 线程 ID
if (redis.call('hexists', KEYS\[1], ARGV\[2]) == 1) then
return redis.call('pexpire', KEYS\[1], ARGV\[1]);
end;
return 0;
该脚本检查锁是否存在且属于当前线程,若是则重置过期时间。
五、自动续期的关键源码
在 RedissonLock
类中,续期逻辑主要由 scheduleExpirationRenewal()
方法触发:
ini
private void scheduleExpirationRenewal(long threadId) {
ExpirationEntry entry = new ExpirationEntry();
ExpirationEntry oldEntry = EXPIRATION\_RENEWAL\_MAP.putIfAbsent(getEntryName(), entry);
// ... 省略部分代码
 
// 创建定时任务,每10秒执行一次续期
Timeout task = commandExecutor.getConnectionManager().newTimeout(
timeout -> {
// 执行续期Lua脚本
RFuture\<Boolean> future = renewExpirationAsync(threadId);
// ... 处理续期结果
}, 
internalLockLeaseTime / 3, // 默认10秒
TimeUnit.MILLISECONDS
);
 
// 保存定时任务引用,用于解锁时取消
entry.setTimeout(task);
}
六、使用建议
-
**优先使用无参 **
lock()
:若业务执行时间不确定,建议使用lock()
触发看门狗,避免锁提前过期。 -
明确业务执行时间 :若能预估业务耗时,可显式指定过期时间(如
lock(30, TimeUnit.SECONDS)
),减少续期开销。 -
避免长事务:看门狗虽能防止锁过期,但长事务仍会导致资源长时间被占用,建议优化业务逻辑。
-
异常处理 :确保在业务异常时释放锁(如使用
try-finally
),避免续期任务无限执行。
七、总结
看门狗机制通过定时续期解决了分布式锁的 **"锁过期与业务执行时间不匹配"** 问题,使 Redisson 锁在复杂业务场景下更加可靠。但需注意合理使用,避免过度依赖续期导致资源长期占用。