Redission try lock 参数你真的理解吗?

564 阅读3分钟

一、前言

前几天同事用redission的lock功能,耗时一下午,也没实现抢不到锁立即返回的效果,然后退回使用redis template的setnx方法。review全部代码后发现,老哥这是没有理解redission lock的参数含义,还有个其他C端场景模块代码毫无作用的等待2s,导致并发度始终上不去。当时有点血压飙升,抄起水杯,喝两口压压惊(自己年龄大了要稳重,业务上可能确实着急同事没有仔细研究这块)。今天简单说下这俩参数。

569a0a42e19c45a0a2940d47c3afde2b.jpeg

二、业务场景

这个业务存在很多任务队列,每个队列里有很多任务,串行执行,且队列中的任务一直增加。线程需要抢队列id锁,然后执行该队列的任务。执行任务的时候,执行失败,其他线程可以抢锁重试。队列加入新任务后会唤醒线程消费队列任务。以下是模拟当时抢锁的代码

    RLock rLock = redissonClient.getLock("queue-lock" + queue.getId());
        boolean islock = false;
        try {
            //islock = rLock.tryLock(4, 5, TimeUnit.SECONDS);
            islock = rLock.tryLock(5,  TimeUnit.SECONDS);
            if (!islock) {
                return ResponseEntity.ok("该任务在执行。。。");
            }
            // 业务代码,耗时操作
            return result;
​
        } catch (Exception e) {
            log.info("\n{} --> 获取锁失败:{}", Thread.currentThread().getName(), e);
        } finally {
                rLock.unlock();
        }

实际效果是:本来应该2s完成的任务,经常5秒都执行不完,研发自测始终不通过,被迫换redistemplate的原生setnx作为锁,然后达到了要求

三、血压飙升,我来分析原因

第一步:RLock rLock = redissonClient.getLock("queue-lock" + queue.getId()),这步源码如下:

    public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
        super(commandExecutor, name);
        this.commandExecutor = commandExecutor;
        this.internalLockLeaseTime = getServiceManager().getCfg().getLockWatchdogTimeout();
        this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();
    }

每个方法都查看过+埋点查看,没有耗时操作

第二步:islock = rLock.tryLock(4, 5, TimeUnit.SECONDS);。这步看着就很可疑。waittime是啥含义,leaseTime怎么用?

官方解释如下:

Tries to acquire the lock with defined leaseTime. Waits up to defined waitTime if necessary until the lock became available. Lock will be released automatically after defined leaseTime interval.
Params:
waitTime – the maximum time to acquire the lock 
leaseTime – lease time 
unit – time unit
Returns:
true if lock is successfully acquired, otherwise false if lock is already set.

waitTime和unit还算好理解,没有抢到锁,则最长等待多少单位的时间。leaseTime有点懵,那就看下源码

第三步、leaseTime源码

设置leaseTime,tryAcquire核心代码如下

<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
                "if ((redis.call('exists', KEYS[1]) == 0) " +
                            "or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then " +
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                    "end; " +
                    "return redis.call('pttl', KEYS[1]);",
                Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
    }

单步过程跳过,走到了这里:redis.call('pexpire', KEYS[1], ARGV[1]),也就是说,这里的lua脚本会设置锁的释放时间为leaseTime,然后返回了一个过期时间。没有设置走如下逻辑,会启动看门狗机制


private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    RFuture<Long> ttlRemainingFuture;
    if (leaseTime != -1) {
        ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    } else {
        // 这里需要注意的是leaseTime==-1,会触发redisson看门狗机制
        ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    }
    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
        if (e != null) {
            return;
        }
​
        // 获取锁成功
        if (ttlRemaining == null) {
            if (leaseTime != -1) {
                internalLockLeaseTime = unit.toMillis(leaseTime);
            } else {
                // 锁自动续时(看门狗机制)触发条件leaseTime == -1
                scheduleExpirationRenewal(threadId);
            }
        }
    });
    return ttlRemainingFuture;
}

四、总结

根据上面的源码分析,如果实现setnx立即返回的效果,可以如下设置

1、固定超时时间,则可以这样设置(30为超时时间):

 islock = rLock.tryLock(0, 30, TimeUnit.SECONDS);

2、启动看门狗机制:

 islock = rLock.tryLock(0, TimeUnit.SECONDS);

注:年龄大了,一定要想的开,公司本身制度混乱,业务激进,能实现就可以了,业务真的其实不关心咋实现的,哪怕Excel手动管理; 谁没年轻过,有时间在优化,问题都是外部的,😁