Saki's 研究记录

watchdog 机制

字数统计: 1.4k阅读时长: 4 min
2023/09/10

看门狗啥意思

如果业务代码没执行完,锁却过期了,这时候其他线程又能抢锁了,线程就不安全啦。所以 redisson内部有个 watchdog(看门狗)的自动延期的机制,意思是定时监测业务是否执行结束,没结束的话你这个锁是不是快到期了(超过锁的三分之一时间,比如设置的 30s过期,现在还剩 10s到期),那就自动续期直到任务结束释放锁,大概是这么个意思。这样做可以防止如果业务代码没执行完,锁却过期了所带来的线程不安全问题。

实际开发中:

1
2
3
4
// 具有 Watch Dog 自动延期机制 默认续 30s 每隔 30/3=10 秒续到 30s
lock.lock();
// 尝试拿锁 10s 后停止重试,返回 false 具有 Watch Dog 自动延期机制 默认续 30s
boolean res = lock.tryLock(10, TimeUnit.SECONDS);
  • 第 2 种很好理解,最长持有锁就 10s,超过就释放。
  • 第 1 种就会走 watchdog(看门狗)自动延期这个策略。但是这里是不是就矛盾了,如果业务上有 bug,进到死循环或宕机了,那锁还在不断自动延期,锁不释放那不就造成死锁了?

所以,所以业务上最好是用 tryLock 而不是 lock

用到 redisson 最多的场景一定是分布式锁,一个基础的分布式锁具有三个特性:

  • 互斥:在分布式高并发的条件下,需要保证,同一时刻只能有一个线程获得锁,这是最最基本的一点。
  • 防止死锁:在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来得及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。
  • 可重入(非必要、但重要):我们知道ReentrantLock是可重入锁,那它的特点就是同一个线程可以重复拿到同一个资源的锁。

实现的方案有很多,这里,就以我们平时在网上常看到的 redis分布式锁方案为例,来对比看看 redisson提供的分布式锁有什么高级的地方。

普通的 Redis 分布式锁的缺陷

我们在网上看到的 redis分布式锁的工具方法,大都满足互斥、防止死锁的特性,有些工具方法会满足可重入特性。

如果只满足上述 3种特性会有哪些隐患呢? redis分布式锁无法自动续期,比如,一个锁设置了1分钟超时释放,如果拿到这个锁的线程在一分钟内没有执行完毕,那么这个锁就会被其他线程拿到,可能会导致严重的线上问题,我已经在秒杀系统故障排查文章中,看到好多因为这个缺陷导致的超卖了。

Redisson 提供的分布式锁

Redisson 提供的分布式锁

redisson锁的加锁机制如上图所示,线程去获取锁,获取成功则执行 lua脚本,保存数据到 redis数据库。

如果获取失败: 一直通过 while循环尝试获取锁(可自定义等待时间,超时后返回失败),获取成功后,执行 lua脚本,保存数据到 redis数据库。

redisson提供的分布式锁是支持锁自动续期的,也就是说,如果线程仍旧没有执行完,那么 redisson会自动给 redis中的目标 key延长超时时间,这在 redisson中称之为 watchdog机制。

同时 redisson还有公平锁、读写锁的实现。

watch dog 的自动延期机制

如果拿到分布式锁的节点宕机,且这个锁正好处于锁住的状态时,会出现锁死的状态,为了避免这种情况的发生,锁都会设置一个过期时间。这样也存在一个问题,加入一个线程拿到了锁设置了 30s超时,在 30s后这个线程还没有执行完毕,锁超时释放了,就会导致问题, redisson给出了自己的答案,就是 watchdog自动延期机制。

redisson提供了一个监控锁的看门狗,它的作用是在 redisson实例被关闭前,不断的延长锁的有效期,也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断的延长锁超时时间,锁不会因为超时而被释放。

默认情况下,看门狗的续期时间是 30s,也可以通过修改 Config.lockWatchdogTimeout来另行指定。

另外 redisson还提供了可以指定 leaseTime参数的加锁方法来指定加锁的时间。超过这个时间后锁便自动解开了,不会延长锁的有效期。

关键结论

上述源码读过来我们可以记住几个关键情报:

  1. watchdog在当前节点存活时每 10s给分布式锁的key续期 30s
  2. watchdog机制启动,且代码中没有释放锁操作时, watchdog会不断的给锁续期;
  3. 从可 2得出,如果程序释放锁操作时因为异常没有被执行,那么锁无法被释放,所以释放锁操作一定要放到 finally {}中;

可以得出结论:

如果释放锁操作本身异常了, watchdog还会不停的续期吗?不会,因为无论释放锁操作是否成功, EXPIRATION_RENEWAL_MAP中的目标 ExpirationEntry对象已经被移除了, watchdog通过判断后就不会继续给锁续期了。

以上。

CATALOG
  1. 1. 看门狗啥意思
  2. 2. 普通的 Redis 分布式锁的缺陷
  3. 3. Redisson 提供的分布式锁
  4. 4. watch dog 的自动延期机制
  5. 5. 关键结论
  6. 6. 可以得出结论: