冷饭新炒:理解Redisson中分布式锁的实现 (6)

剩下一个scheduleExpirationRenewal(threadId)方法还没有分析,里面的逻辑就是看门狗的定期续期逻辑:

// 基于线程ID定时调度和续期 private void scheduleExpirationRenewal(long threadId) { // 如果需要的话新建一个ExpirationEntry记录线程重入计数,同时把续期的任务Timeout对象保存在属性中 ExpirationEntry entry = new ExpirationEntry(); ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry); if (oldEntry != null) { // 当前进行的当前线程重入加锁 oldEntry.addThreadId(threadId); } else { // 当前进行的当前线程首次加锁 entry.addThreadId(threadId); // 首次新建ExpirationEntry需要触发续期方法,记录续期的任务句柄 renewExpiration(); } } // 处理续期 private void renewExpiration() { // 根据entryName获取ExpirationEntry实例,如果为空,说明在cancelExpirationRenewal()方法已经被移除,一般是解锁的时候触发 ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ee == null) { return; } // 新建一个定时任务,这个就是看门狗的实现,io.netty.util.Timeout是Netty结合时间轮使用的定时任务实例 Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { // 这里是重复外面的那个逻辑, ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ent == null) { return; } // 获取ExpirationEntry中首个线程ID,如果为空说明调用过cancelExpirationRenewal()方法清空持有的线程重入计数,一般是锁已经释放的场景 Long threadId = ent.getFirstThreadId(); if (threadId == null) { return; } // 向Redis异步发送续期的命令 RFuture<Boolean> future = renewExpirationAsync(threadId); future.onComplete((res, e) -> { // 抛出异常,续期失败,只打印日志和直接终止任务 if (e != null) { log.error("Can't update lock " + getName() + " expiration", e); return; } // 返回true证明续期成功,则递归调用续期方法(重新调度自己),续期失败说明对应的锁已经不存在,直接返回,不再递归 if (res) { // reschedule itself renewExpiration(); } }); } }, // 这里的执行频率为leaseTime转换为ms单位下的三分之一,由于leaseTime初始值为-1的情况下才会进入续期逻辑,那么这里的执行频率为lockWatchdogTimeout的三分之一 internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); // ExpirationEntry实例持有调度任务实例 ee.setTimeout(task); } // 调用Redis,执行Lua脚本,进行异步续期 protected RFuture<Boolean> renewExpirationAsync(long threadId) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return 1; " + "end; " + "return 0;", Collections.<Object>singletonList(getName()), // 这里根据前面的分析,internalLockLeaseTime在leaseTime的值为-1的前提下,对应值为lockWatchdogTimeout internalLockLeaseTime, getLockName(threadId)); }

基于源码推断出续期的机制由入参leaseTime决定:

当leaseTime == -1的前提下(一般是lock()和lockInterruptibly()这类方法调用),续期任务的调度周期为lockWatchdogTimeout / 3,锁的最大持有时间(KEY的过期时间)被刷新为lockWatchdogTimeout

当leaseTime != -1的前提下(一般是lock(long leaseTime, TimeUnit unit)和lockInterruptibly(long leaseTime, TimeUnit unit)这类方法调用指定leaseTime不为-1),这种情况下会直接设置锁的过期时间为输入值转换为ms单位的时间量,不会启动续期机制

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzsws.html