Redis 本身原生没有看门狗机制。
看门狗(Watch Dog)是 Redisson 实现分布式锁时自带的自动续期机制
不是 Redis 服务端命令。
Redis 原生分布式锁:SET key value NX EX ttl
- 锁设置固定过期时间(默认30s);
- 如果业务执行时间 > 锁过期时间 → 锁自动释放,并发安全失效。
看门狗解决什么问题
问题场景:
- 加锁,设置过期时间 30s
- 业务代码执行很慢(GC、IO 阻塞、慢查询),超过 30s 还没执行完、没释放锁
- Redis 自动把锁删掉
- 其他线程拿到同一把锁 →锁失效,并发问题
看门狗目标:
只要持有锁的业务线程还没释放锁,就持续延长锁的有效期,防止锁提前过期。
底层原理(Redisson)
不是全靠 Lua
Lua 只是【锁判断、续期、释放】这些原子操作的载体
看门狗整体整套逻辑,大量是 Java 客户端代码
底层:
使用:
- 引入 redisson 依赖,创建 RedissonClient 客户端。
redissonClient.getLock(lockKey)获取 RLock 分布式锁(可重入)。lock()无参:开启看门狗,自动续期;带 leaseTime 参数:看门狗关闭,锁到期自动释放。tryLock():尝试抢锁;只传等待时间开看门狗,等待 + 持有时间都传则关闭看门狗。- 业务放 try 里,finally 中先判断
isHeldByCurrentThread(),再 unlock,防止别的线程解锁报错。
底层:加锁、续期、解锁靠 Lua 保证原子;看门狗是客户端 Netty 定时任务。
宕机后看门狗停止,锁到时间自动释放,不会死锁。
public class RedissonSimpleDemo { // 引入redisson依赖,创建RedissonClient客户端 public static RedissonClient getRedissonClient() { Config config = new Config(); config.useSingleServer() .setAddress("redis://127.0.0.1:6379"); return Redisson.create(config); } public static void main(String[] args) throws InterruptedException { RedissonClient redissonClient = getRedissonClient(); String lockKey = "business:lock"; // redissonClient.getLock(lockKey) 获取RLock分布式锁(可重入) //RLock在Redis是Hash结构 RLock lock = redissonClient.getLock(lockKey); try { // lock()无参:开启看门狗,自动续期 lock.lock(); System.out.println("执行业务"); TimeUnit.SECONDS.sleep(35); } finally { // finally中先判断isHeldByCurrentThread(),再unlock,防止别的线程解锁报错 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } // tryLock():尝试抢锁;只传等待时间开看门狗 boolean tryLockResult = false; RLock lock2 = redissonClient.getLock(lockKey); try { tryLockResult = lock2.tryLock(10, TimeUnit.SECONDS); if (tryLockResult) { System.out.println("tryLock执行业务"); TimeUnit.SECONDS.sleep(25); } } finally { if (tryLockResult && lock2.isHeldByCurrentThread()) { lock2.unlock(); } } // tryLock():等待+持有时间都传则关闭看门狗 boolean tryLockResult2 = false; RLock lock3 = redissonClient.getLock(lockKey); try { tryLockResult2 = lock3.tryLock(10,15, TimeUnit.SECONDS); if (tryLockResult2) { System.out.println("指定leaseTime执行业务,看门狗关闭"); TimeUnit.SECONDS.sleep(20); } } finally { if (tryLockResult2 && lock3.isHeldByCurrentThread()) { lock3.unlock(); } } redissonClient.shutdown(); } }tryLock()
尝试抢锁,不会一直阻塞死等,抢到返回 true(锁重入了,field计数+1),否则返回 false
有两种常用重载:
tryLock(waitTime, TimeUnit):最多等待 waitTime 时间抢锁,抢到就持有,开启看门狗tryLock(waitTime,leaseTime, TimeUnit):最多等 waitTime;抢到之后锁最多持有 leaseTime,关闭看门狗,不会自动续期waitTime:愿意花多久等锁;leaseTime:锁的有效期
误区:锁重入关闭看门狗指的是本次加锁不开启看门狗还是说锁重入之前那次加的锁的看门狗也关闭?
重入不会把之前已经启动的看门狗关掉!只是:这次重入这一次,不会再额外新建一个看门狗。
unlock()
释放当前线程持有的这把分布式锁。
- 底层走 Lua 脚本:hash 重入计数减一,计数到 0 就删除锁 key,同时停止看门狗定时任务
- ⚠️ 只能自己解自己的锁;别的线程去 unlock 会报错,所以代码里要配合
isHeldByCurrentThread()判断
复习:
- try:放需要执行的业务代码(加锁后的逻辑),正常跑、或者抛出异常,都会往下走
- finally:无论 try 里面正常结束、还是抛异常、还是 return,finally 代码几乎一定会执行
Redisson 的锁结构不是简单 string,是hash 结构:
- key:锁名称
- field:当前线程唯一 ID
- value:重入计数(支持可重入锁)
好处:可以精准判断:这把锁是不是当前这个线程持有的,防止续别人的锁
豆包讲全流程: