• Redisson的看门狗watchDog机制是怎么实现的?


    回顾下怎么加锁

    RLock lock = redisson.getLock("myLock");
    lock.lock();
    
    
    • 1
    • 2
    • 3

    lock干了啥

    private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
        Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
        // 加锁成功
        if (ttl == null) {
            return;
        }
        // 加锁失败,while(true)等待重试。
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看到 lock 主要是请求 tryAcquire (-1,-1, null , threadId )来完成加锁逻辑,然后判断加锁成功与否,失败的话就重试。目前还没发现 watchDog 的机制,那我们继续追下去,看看如何加锁的?

    private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
    }
    
    // watchDog机制在这里
    // -1, -1, null,threadId
    private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        RFuture<Boolean> ttlRemainingFuture;
        // 省略一些请求lua加锁代码。之前都分析过。
    
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            // ttlRemaining为true代表加锁成功。
            if (ttlRemaining == null) {
          			// 接下来是什么鬼逻辑?
                // leaseTime == -1就scheduleExpirationRenewal开启看门狗续期,
                // leaseTime != -1就不续期,只是把internalLockLeaseTime时间变成传进来的时间。
                // 这里疑点重重:
                // 1.什么时候leaseTime != -1?
                // 2.不是所有的lock()方法都有看门狗机制?
                if (leaseTime != -1) {
                    internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
                    scheduleExpirationRenewal(threadId);
                }
            }
        });
        return ttlRemainingFuture;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    意外收获!好像不是所有的 lock ()都有看门狗,因为看到条件判断 lease Time =-1的时候才开启看门狗线程,不等于﹣1的时候就没有这个
    机制。那什么时候不等于﹣1呢?
    回答这个问题前我们可以先推测下,-1是哪来的?是 lock ()入口默认带来的:

    @Override
    public void lock() {
        try {
            // -1 !!!
            lock(-1, null, false);
        } catch (InterruptedException e) {
            throw new IllegalStateException();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    带时间参数的lock方法:

    @Override
    public void lock(long leaseTime, TimeUnit unit) {
        try {
            lock(leaseTime, unit, false);
        } catch (InterruptedException e) {
            throw new IllegalStateException();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    时间传-1就会开启看门狗机制
    现在知道 watchDog 何时生效了,那继续看下他是怎么工作的?
    上文可以发现续期的代码在这个方法里面: scheduleExpirationRenewal ( threadId );这个方法底层是靠 renewExpiration 来完成续期的。

    private void renewExpiration() {
        ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (ee == null) {
            return;
        }
    	
        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;
                }
    			// 调用lua脚本进行续期
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.onComplete((res, e) -> {
                    // 报异常就移除key
                    if (e != null) {
                        log.error("Can't update lock " + getRawName() + " expiration", e);
                        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                        return;
                    }
    				// 续期成功的话就下一轮续期。
                    if (res) {
                        // reschedule itself
                        renewExpiration();
                    } else {
                        // 续期失败的话就取消续期,移除key等操作
                        cancelExpirationRenewal(null);
                    }
                });
            }
            // 这里是个知识点,续期线程在过期时间达到三分之一的时候工作,比如9s过期时间,那么续期会在第3秒的时候工作,也就是还剩余6s的时候进行续期
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
    
        ee.setTimeout(task);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    这里有四个关键点:

    • 续期核心 lua 脚本在 renewExpirationAsync 里
    • 续期成功自己调用自己,也就是为下一次续期做准备
    • 续期失败就取消续期,移除 key 等操作
    • 续期的开始时间是超过过期时间的三分之一,比如9s过期时间,那么第3s的时候开始续期。
      所以重点看下续期的 lua 源代码:
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    很简单就是看当前线程有没有加锁 hexists , KEYS [1], ARGV [2])==1,有加锁的话就代表业务线程还没执行完,就给他的锁重新续期 pexpire ', KEYS [1], ARGV [1],然后返回1,也就是 true ,没加锁的话返回0,也就是 alse 。
    那就是返回1就调用自己准备下一次续期: renewExpiration ();,返回0就调用 cancelExpirationRenewal ( null );取消续期,删除 key 等操作。
    在这里插入图片描述

    需要注意的点:

    • watchDog 并不是全部 lock 都生效,而是 lock 没设置过期时间的那些锁才会开启 watchDog 续期,没设置过期时间的话默认采取的是 watchDog 的30s过期时间。如果调用 lock ( time , unit )是不会开启 watchDog 线程续期的,是有可能造成线程不安全的。
    • 续期是段 lua 脚本。
    • 续期线程会在续期时间超过三分之一的时候执行。
      疑问:不会浪费性能吗?每个方法都起个看门狗线程,这个影响有多大?
      比篇讲述了看门狗是怎么工作的,他的核心原理我们一清二楚,整个加锁的这块流程算是告一段落了,接下来我们需要知道锁是怎么释放
      的?下篇分析!
  • 相关阅读:
    微服务真的需要前后端分离吗?
    Python开发工具PyCharm全新版本V2022.2即将发布
    Python | eval、exec | NameError: name ‘XXX‘ is not defined`
    C# CAD备忘录
    Zabbix Proxy实现跨机房分布式监控
    开发工具安装
    带你玩转 Redis 的 SortedSet 数据类型
    java中什么是不可变类
    C与C++字符串分割方法示例汇总
    「互动有礼,感谢有你」参与互动就有机会获赠 Navicat Premium 16
  • 原文地址:https://blog.csdn.net/miaoao611/article/details/126890297