1. 首页
  2. IT资讯

分布式锁-Redisson-Lock锁的使用与原理

添加 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”; }

2|0分析

当我们发送 /hello 请求后等待 20 秒得到响应结果,会在 Redis 中存储锁的信息(如下图所示),期间,其它用户发送 /hello 请求时会被阻塞,只有前一个请求结束后释放锁,当前请求才会进入。

分布式锁-Redisson-Lock锁的使用与原理

思考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; }

3|0总结

1、lock 锁是线程阻塞的

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

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

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

本文来自投稿,不代表程序员编程网立场,如若转载,请注明出处:http://www.cxybcw.com/197587.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code