首页 科技正文

衢州19楼论坛:分布式锁-Redisson-Lock锁的使用与原理

admin 科技 2020-05-15 51 0

环境准备

添加 Maven 依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.0</version>
</dependency>

添加设置类

@Configuration
public class MyRedissonConfig {
    @Bean(destroyMethod = "shutdown")
    RedissonClient redisson() throws IOException {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.56.10:6379");
        return Redisson.create(config);
    }
}

基本使用代码如下:

@GetMapping("/hello")
@ResponseBody
public String hello() {
    //获取Lock锁,设置锁的名称
    RLock lock = redisson.getLock("my-lock");
    //开启
    lock.lock();
    try {
        System.out.println("上锁:" + Thread.currentThread().getId());
        //模拟营业处置20秒
        TimeUnit.SECONDS.sleep(20);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }finally {
        System.out.println("解锁:" + Thread.currentThread().getId());
        //释放
        lock.unlock();
    }
    return "hello";
}

剖析

当我们发送 /hello 请求后守候 20 秒获得响应效果,会在 Redis 中存储锁的信息(如下图所示),时代,其它用户发送 /hello 请求时会被壅闭,只有前一个请求竣事后释放锁,当前请求才会进入。

思索1:若是在营业处置过程中程序突然终止,锁没有获得释放,是否会一直壅闭下去?

经由实验,在营业处置的20秒中,将服务手动住手,刷新 Redis 中 my-lock 的信息,发现 TTL 不停的减小,直到失效,发送其它请求能够正常执行,这说明,纵然不释放锁,Redis 设置的过时时间到了也会自动删除锁的信息。源码如下:

//获取当前线程id
long threadId = Thread.currentThread().getId();
//获取此线程的锁
Long ttl = tryAcquire(leaseTime, unit, threadId);
//若是获取不到,则说明锁已经释放了,直接返回
if (ttl == null) {
    return;
}
while (true) {
    ttl = tryAcquire(leaseTime, unit, threadId);
    //判断是否能获取到锁
    if (ttl == null) {
        break;
    }
    ...
}

思索2:过时时间是多少?若是我们的营业处置时间超过了过时时间,岂不是还没处置完就把锁的信息给删了?

正常启动服务接见 /hello,刷新 my-lock 的信息,我们发现,TTL 每次削减到 20 就再次变为 30,直到营业处置完成,my-lock 被删除。查找相关源代码如下:

while (true) {
    //实验获取锁
    ttl = tryAcquire(leaseTime, unit, threadId);
    //若是获取不到,说明执行该线程执行竣事,就终止循环
    if (ttl == null) {
        break;
    }

    //若是获取到了就继续循环
    if (ttl >= 0) {
        try {
            future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            if (interruptibly) {
                throw e;
            }
            future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
        }
    } else {
        if (interruptibly) {
            future.getNow().getLatch().acquire();
        } else {
            future.getNow().getLatch().acquireUninterruptibly();
        }
    }
}

继续深入源码可以看到,若是不指定锁的时间,就默以为 30 秒,它有一个好听的名字:看门狗

private long lockWatchdogTimeout = 30 * 1000;

只要占领锁,就会启动一个准时义务:每隔一段时间重新给锁设置过时时间

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, getLockName(threadId));
    //internalLockLeaseTime就是看门狗的时间
}

每隔多恒久刷新一下呢?

//获取看门狗的时间,赋值给自己
this.internalLockLeaseTime = xxx.getLockWatchdogTimeout();
public long getLockWatchdogTimeout() {
    return lockWatchdogTimeout;
}

Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
    @Override
    public void run(Timeout timeout) throws Exception {
        ...
    }
    //使用的时刻除3,也就是10秒刷新一次
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

思索三:若何自定义过时时间?

lock() 方式另有一个重载方式,可以传入过时时间和单元

void lock(long leaseTime, TimeUnit unit);

我们将之前的代码修改,设置为 15 秒,重启服务再测试

lock.lock(15, TimeUnit.SECONDS);

接见 /hello,刷新 Redis 中 my-lock 的信息会发现,TTL 从 15 减到 0,然后锁信息过时,并不会泛起之前的 10秒一刷新,查看源码:

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
    //若是传入了过时时间,则直接执行tryLockInnerAsync内里的Lua剧本
    if (leaseTime != -1) {
        return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    }
    //没有传入过时时间,执行下面的逻辑
    RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
        //有异常,直接返回
        if (e != null) {
            return;
        }
        if (ttlRemaining == null) {
            //刷新过时时间
            scheduleExpirationRenewal(threadId);
        }
    });
    return ttlRemainingFuture;
}

总结

1、lock 锁是线程壅闭的

2、使用 lock 的无参方式,锁的默认时间是 30 秒,并且会每隔 10 秒刷新为 30 秒,只要营业没执行完,就会一直续期,若是执行完成或者突然中止,则不会再续期,到达过时时间就释放锁

3、使用 lock 的有参方式指准时间,到达指准时间会自动解锁,因此设置的时间必须大于营业执行时间,否则,营业没执行完,锁就会被释放

4、推荐使用指准时间的方式,省掉了续期操作,但需要合理设置过时时间,不能过早的使锁释放

,

Sunbet

Sunbet www.orljy.com sunbet,老品牌,有信誉,精彩尽在sunbet。

版权声明

本文仅代表作者观点,
不代表本站dafa888的立场。
本文系作者授权发表,未经许可,不得转载。

评论