一文看懂Redisson分布式锁的Watchdog机制源码实现( 二 )

在该方法内部,首先通过 getEntryName() 获取当前锁实例的名称,然后从 EXPIRATION_RENEWAL_MAP 中查找该名称对应的 ExpirationEntry 对象 。如果对象不存在,则直接返回即可;否则,创建一个新的定时任务,该任务会在 internalLockLeaseTime / 3 毫秒后执行,并尝试异步更新锁的过期时间 。如果更新成功,则会再次调用 renewExpiration() 方法,以便持续延长锁的过期时间 。这个定时任务通俗的讲就是所谓的看门狗 。当然,这里更新过期时间的操作是通过调用 renewExpirationAsync() 实现的,它仍然是一个异步操作 。
4. renewExpirationAsync
protected RFuture<Boolean> renewExpirationAsync(long threadId) {// 使用 evalWriteAsync 方法执行 EVAL 命令return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,// EVAL 命令的脚本,根据给定的键和参数进行判断和更新"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +// 如果指定的键和参数都存在,则续约该键的过期时间"redis.call('pexpire', KEYS[1], ARGV[1]); " +// 并返回操作成功的标志"return 1; " +"end; " +// 如果指定的键和参数不匹配,则返回操作失败的标志"return 0;",// EVAL 命令中用到的键Collections.<Object>singletonList(getName()),// 续约的过期时间,在 Redisson 中为 internalLockLeaseTimeinternalLockLeaseTime,// 获取锁的名称,由线程 ID 和当前 Redisson 实例 ID 组成getLockName(threadId));}此方法主要是用于在获取分布式锁的情况下,对锁的过期时间进行续约的操作 。其中,
RedisCommands.EVAL_BOOLEAN 代表执行 EVAL 命令后返回的数据类型为 Boolean 类型;getKey() 方法用于获取锁的名称,该名称由锁的前缀和锁的 ID 组成;getLockName(threadId) 方法用于获取当前线程获取锁后的锁名称,之所以要使用当前 Redisson 实例 ID 与线程 ID 组合作为锁名称,是为了确保在多个 Redisson 实例下,所有线程都能够正确地获取到锁名并正确地执行续约操作 。
5.cancelExpirationRenewal
void cancelExpirationRenewal(Long threadId) {// 从 EXPIRATION_RENEWAL_MAP 中查找当前锁实例,并获取相应的 ExpirationEntry 对象 。ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (task == null) {return;}// 如果传入了调用者线程 ID,则从任务中移除该线程 ID 。if (threadId != null) {task.removeThreadId(threadId);}// 如果线程 ID 为空,或者任务已经没有任何线程在持有它了,则取消任务并从 EXPIRATION_RENEWAL_MAP 中删除该实体 。if (threadId == null || task.hasNoThreads()) {Timeout timeout = task.getTimeout();if (timeout != null) {timeout.cancel(); // 取消定时任务}EXPIRATION_RENEWAL_MAP.remove(getEntryName()); // 从 EXPIRATION_RENEWAL_MAP 中删除实体}}这个方法主要是用于取消延长锁过期时间的定时任务 。当一个线程在unlock释放锁时,便会调用这个方法 。在该方法内部,首先通过 getEntryName() 获取当前锁实例的名称,然后从 EXPIRATION_RENEWAL_MAP 中查找该名称对应的 ExpirationEntry 对象 。如果对象不存在,则直接返回即可;否则,如果传入了调用者线程 ID,则从任务中移除该线程 ID 。接着,如果线程 ID 为空,或者任务已经没有任何线程在持有它了,则取消任务并从 EXPIRATION_RENEWAL_MAP 中删除该实体 。需要注意的是,取消定时任务的操作是通过调用 timeout.cancel() 实现的,它会将定时任务从时间轮中移除 。由于 Redisson 是基于.NETty 的,所以它使用的是 HashedWheelTimer,这个定时器底层是基于时间轮实现的,并且支持动态添加和删除定时任务 。
三、 使用注意事项使用Redisson分布式锁的看门狗应注意以下几个问题:

  • 设置合理的锁超时时间:如果锁的超时时间过短,则会导致频繁续命和多次加锁解锁,影响程序性能;如果锁的超时时间过长,则可能会因为某些异常原因使得锁无法释放,从而导致死锁 。
  • 在业务逻辑完成后及时启动看门狗:如果业务逻辑执行时间过长,则有可能导致锁的过期,从而使看门狗失去续命的意义 。
  • 合理配置看门狗的参数:看门狗的续命时间间隔应该在锁的过期时间内,且重试次数不宜过多,以免影响程序性能 。
  • 避免锁的嵌套使用:锁的嵌套使用有可能导致死锁或者其他并发问题,应避免使用 。




推荐阅读