在基于 Redis 的分布式锁方案中,最让人头疼的并非加锁失败,而是业务还没跑完锁就过期了。Redisson 客户端内置的看门狗(WatchDog)机制正是为解决这个问题而生,它能在不指定过期时间的情况下,默默帮我们延长锁的持有周期。

一、为什么高并发下需要自动延期
在订单创建、券池扣减等流量高峰接口中,一次请求背后可能要调用多个下游服务。如果我们在加锁时写死十秒过期,而某次 GC 停顿或数据库慢查询让业务跑了十二秒,锁就会提前释放。此时另一个线程拿到锁进入临界区,两个线程同时修改同一行数据,数据一致性直接被破坏。
有人会说:把过期时间设长一点不就行了?但过长的解锁时间意味着故障时恢复更慢,而且你永远无法准确预估最坏情况下的业务耗时。WatchDog 的思路是:只要线程还活着且在干活,锁就跟着活;线程退出或宕机,续期停止,锁自然释放。这种自适应续期比拍脑袋定超时更稳妥。
二、WatchDog 的启动条件与默认行为
Redisson 并不是每次加锁都会开启看门狗。只有在调用不传 leaseTime 参数的重载方法时,客户端才会认为你需要自动延期。例如下面这段代码会触发 WatchDog,而指定了三秒租约的则不会。
// 会启动 WatchDog,默认 30s 过期,每隔 10s 续期一次
RLock lock = redissonClient.getLock("order_lock");
lock.lock();
// 明确指定租约时间,WatchDog 不生效
RLock lock2 = redissonClient.getLock("order_lock");
lock2.lock(3, TimeUnit.SECONDS);
默认配置下,锁的初始有效期是三十秒,WatchDog 每隔三分之一时间也就是十秒,发起一次 Lua 脚本续期,把有效期重新刷回三十秒。只要持有锁的客户端 JVM 没有挂掉,且线程未主动 unlock,这个循环就会一直进行。
如果你希望修改默认周期,可以通过修改 Config 中的 lockWatchdogTimeout 参数实现,例如调整为六十秒。注意该值是看门狗判定锁过期的阈值,续期间隔仍是其三分之一。
Config config = new Config(); config.setLockWatchdogTimeout(60 * 1000); RedissonClient client = Redisson.create(config);
三、续期背后的线程模型
Redisson 使用 Netty 的定时任务线程池来调度续期动作。当某个线程成功获取到锁后,客户端会为该锁记录一个 Entry,里面包含锁名、线程 ID 和续期任务。调度器到点后执行一段原子 Lua 脚本,判断当前锁是否仍由该线程持有,如果是则执行 pexpire 命令重置时间。
-- 伪代码逻辑的 Lua 续期脚本
if redis.call("hexists", KEYS[1], ARGV[2]) == 1 then
redis.call("pexpire", KEYS[1], ARGV[1])
return 1
end
return 0
这种设计的优点是续期与业务线程解耦:即使业务线程被阻塞,只要 JVM 存活,Netty 线程依旧能发出续期请求。但如果整个进程崩溃,定时任务随之消亡,锁在最后一个周期结束后自动失效,不会造成死锁。相比自己手写一个定时线程,Redisson 的方案在异常退出时更干净。
四、与手动设置 leaseTime 的对比
很多初学者分不清 lock() 和 lock(long, TimeUnit) 的差异。下表列出两者在高频场景中的表现:
| 方式 | 是否自动续期 | 风险点 | 适用场景 |
|---|---|---|---|
| lock() | 是(WatchDog) | 忘记 unlock 会一直续期 | 耗时不确定的业务 |
| lock(lease, unit) | 否 | 业务超时锁提前释放 | 耗时可控的短任务 |
从表中可以看出,如果你的接口在双十一零点可能从五十毫秒飙到五秒,用固定租约就像赌运气。而 WatchDog 让锁生命周期跟业务绑定,但代价是必须保证在 finally 块中释放锁,否则会一直占用资源。
另外要注意,WatchDog 仅对 Redisson 原生 RLock 生效。如果你用 RedisTemplate 自己写 SETNX,那就得自己实现续期调度,代码量和边界情况会多出不少。
五、生产环境的最佳实践
首先,永远把 unlock 放在 try-finally 中,这是 WatchDog 安全运转的前提。其次,对于特别重要的临界资源,可以结合业务监控,当单次持锁超过阈值时报警,防止某台机器 GC 长期停顿导致锁被无限续期而其他节点饿死。
RLock lock = redissonClient.getLock("stock_lock");
lock.lock();
try {
// 扣减库存等核心逻辑
deductStock();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
最后,在集群模式下 Redisson 支持 RedLock 算法,但 WatchDog 的续期是针对单实例锁的。如果采用多节点红锁,需要评估网络分区下续期失败的影响,必要时用更短的 watchdogTimeout 来加快失效收敛。
理解 WatchDog 的触发条件和线程模型,能帮你在高并发系统中少踩很多分布式锁的坑。它不是一个银弹,但确实是 Redisson 最实用的默认能力之一。