分布式锁 - Redisson的看门狗(watchdog)机制

本文深入探讨了Redisson的分布式锁实现,特别是其看门狗(watchdog)机制。当未自定义锁的持有时间时,Redisson会默认加锁30秒,并每10秒自动续期。通过`scheduleExpirationRenewal`方法触发看门狗,使用`renewExpirationAsync`异步更新锁的过期时间。该机制确保了锁的有效性和可靠性。

分布式锁 - Redisson的看门狗(watchdog)机制

前言

本篇文章从Redisson的加锁(tryLock)入手,带大家由源码来了解一下watchdog的自动延迟加锁操作,如果对Redisson的加锁机制没有了解,建议可以先看一下本人的另一篇博客分布式锁-Redisson的使用及源码分析

结论

  • 想要触发Redisson看门狗机制,不能自定义 leaseTime(或者传参 -1)
  • Redisson默认加锁30秒,每隔10秒刷新加锁时间
  • watchdog的延时时间 可以由 lockWatchdogTimeout指定默认延时时间,但是不要设置太小
  • Redisson是通过Future和Timeout功能来实现异步延时

源码分析

前文讲到 tryLock() 中最重要的加锁逻辑是 tryAcquire(waitTime, leaseTime, unit, threadId),今天我们就来从加锁的方法入手看一下如何完成WatchDog

Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);

接下来,我们进入这个方法看一下

private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    // get() 方法异步阻塞式获取加锁结果    
    return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
    }

/**
 * 重点方法 看这里
 */
private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        RFuture<Boolean> acquiredFuture;
    
    	// 1、判断锁的持有时间是否由用户自定义(这里我不在细节讨论,感兴趣的童鞋们可以去看上一篇文章)
        if (leaseTime != -1) {
            acquiredFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
        } else {
            // 2、当用户没有自定义锁占有时间时,默认传入 internalLockLeaseTime
            // private long lockWatchdogTimeout = 30 * 1000; 默认30秒
            acquiredFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                    TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
        }

        CompletionStage<Boolean> f = acquiredFuture.thenApply(acquired -> {
            // lock acquired
            if (acquired) {
                if (leaseTime != -1) {
                    // 3、如果用户传入占用时间直接转换,把默认值internalLockLeaseTime 更新为用户自定义的占有时间
                    internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
                    // 4、看这里,看这里,没错,这里就是触发看门狗机制的方法
                    // 重点:只有当 leaseTime == -1时才会触发看门狗机制
                    scheduleExpirationRenewal(threadId);
                }
            }
            return acquired;
        });
        return new CompletableFutureWrapper<>(f);
    }

我们来看一下方法:scheduleExpirationRenewal(threadId);

protected void scheduleExpirationRenewal(long threadId) {
        ExpirationEntry entry = new ExpirationEntry();
    	// 1、EXPIRATION_RENEWAL_MAP 是一个全局的静态常量Map
        ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
        if (oldEntry != null) {
            // 2、oldEntry != null 说明该线程不是第一次触发
            oldEntry.addThreadId(threadId);
        } else {
            // 3、oldEntry == null 说明该线程是第一次触发
            entry.addThreadId(threadId);
            try {
                // 4、更新过期时间
                renewExpiration();
            } finally {
                if (Thread.currentThread().isInterrupted()) {
                    cancelExpirationRenewal(threadId);
                }
            }
        }
    }

接下来,我们一起看一下renewExpiration()是如何更新过期时间的

private void renewExpiration() {
    	// 1、获取当前线程的更新对象
        ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (ee == null) {
            return;
        }
        
    	// 2、创建了一个定时任务
        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;
                }
                Long threadId = ent.getFirstThreadId();
                if (threadId == null) {
                    return;
                }
                
                // 3、异步更新过期时间
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                // 4、res 执行结果,e 异常
                future.whenComplete((res, e) -> {
                    if (e != null) {
                        // 5、如果出现异常,从map中删除,直接返回
                        log.error("Can't update lock " + getRawName() + " expiration", e);
                        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                        return;
                    }
                    
                    if (res) {
                        // 6、如果没有报错,就再次定时延期
                        renewExpiration();
                    } else {
                        // 7、否则取消定时
                        cancelExpirationRenewal(null);
                    }
                });
            }
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
    	// 8、延迟时间为:internalLockLeaseTime / 3 也就是10秒    
    
        ee.setTimeout(task);
    }

// 通过lua脚本判断给key重新设置过期时间
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
        return evalWriteAsync(getRawName(), 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.singletonList(getRawName()),
                internalLockLeaseTime, getLockName(threadId));
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值