小言_互联网的博客

redis分布式锁在项目中的实现二 Redisson的使用,原理及源码解读

509人阅读  评论(0)

Redisson

本文章对应的代码地址:https://github.com/zhangshilin9527/redisson-study

背景

接着上一篇文章《redis分布式锁在项目中的实现一 手动实现redis分布式锁》我们继续说一下redis分布式锁,如果还想继续优化一下上面的Demo3,就需要我们引入一个新的组件Redisson

 

什么是Redisson?

Redisson是最先进,最简单的Redis Java客户端,是基于Redis的对象,集合,锁,同步器和Java上分布式应用程序所需的服务。

快速开始

1.引入maven


  
  1. <dependency>
  2. <groupId>org.redisson </groupId>
  3. <artifactId>redisson </artifactId>
  4. <version>3.5.4 </version>
  5. </dependency>

2.增加配置


  
  1. @Bean
  2. public RedissonClient redisson() {
  3. Config config = new Config();
  4. //redis集群方式
  5. config.useClusterServers().setScanInterval( 2000)
  6. .addNodeAddress( "redis://127.0.0.1:7000").addNodeAddress( "redis://127.0.0.1:7001").setPassword( "abcdef");
  7. return Redisson.create(config);
  8. }

3.代码示例


  
  1. public String redisLockDemo4() {
  2. String redisKey = "redis_key_004";
  3. RLock redissonLock = redissonClient.getLock(redisKey);
  4. try {
  5. redissonLock.lock();
  6. //获取到redis锁,进行业务逻辑
  7. System.out.println( "获取到redis锁");
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. } finally {
  11. redissonLock.unlock();
  12. }
  13. return "done";
  14. }

redisson锁还支持读写锁,红锁等,具体其他使用方法请参照官方地址:https://redisson.org/

Redisson分布式锁原理

我使用一张图来解释一下Redisson怎么获取锁及防止锁失效的。

1.线程1,线程2,线程3同时去获取锁;

2.线程1获取到锁,线程2,线程3未获取到锁;

3.线程2,线程3while循环,进行自旋获取锁

4.线程1后台开启线程,每1/3超时时间执行一次锁延迟动作,防止锁失效。

 

Redisson源码解析

redisson源码使用了大量的lua脚本,使用lua脚本操作redis主要由以下几个有点:

1.减少网络开销;

2.原子操作;

3.保证事务;

不过不用担心,redisson中的lua脚本命令基本上可以看明白,看不明白的通过百度也基本可以确定,下面我们来看一下Redisson是如何加锁的。

我们Redisson是如何获取锁的,主要是通过下面这个方法

org.redisson.RedissonLock#lockInterruptibly(long, java.util.concurrent.TimeUnit)
    org.redisson.RedissonLock#tryAcquireAsync
        org.redisson.RedissonLock#tryLockInnerAsync


  
  1. <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
  2. internalLockLeaseTime = unit.toMillis(leaseTime);
  3. return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
  4. "if (redis.call('exists', KEYS[1]) == 0) then " +
  5. "redis.call('hset', KEYS[1], ARGV[2], 1); " +
  6. "redis.call('pexpire', KEYS[1], ARGV[1]); " +
  7. "return nil; " +
  8. "end; " +
  9. "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
  10. "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
  11. "redis.call('pexpire', KEYS[1], ARGV[1]); " +
  12. "return nil; " +
  13. "end; " +
  14. "return redis.call('pttl', KEYS[1]);",
  15. Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
  16. }

整理一下这个流程:

1.判断是否是当前key是否存在

2.执行hset命令 key 是我们传进来的redisKey,arvg是当前线程,vaule是1 

3.给锁设置时间

4.返回nil

下面的是支持重入锁,可以先不看,这样我们就获取到锁了,但是他是怎么给锁延长超时时间的呢,请看下面代码

当获取到锁之后,就会执行org.redisson.RedissonLock#scheduleExpirationRenewal方法,我们来看一下这个代码


  
  1. private void scheduleExpirationRenewal(final long threadId) {
  2. if (expirationRenewalMap.containsKey(getEntryName())) {
  3. return;
  4. }
  5. Timeout task = commandExecutor.getConnectionManager().newTimeout( new TimerTask() {
  6. @Override
  7. public void run(Timeout timeout) throws Exception {
  8. RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
  9. "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
  10. "redis.call('pexpire', KEYS[1], ARGV[1]); " +
  11. "return 1; " +
  12. "end; " +
  13. "return 0;",
  14. Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
  15. future.addListener( new FutureListener<Boolean>() {
  16. @Override
  17. public void operationComplete(Future<Boolean> future) throws Exception {
  18. expirationRenewalMap.remove(getEntryName());
  19. if (!future.isSuccess()) {
  20. log.error( "Can't update lock " + getName() + " expiration", future.cause());
  21. return;
  22. }
  23. if (future.getNow()) {
  24. // reschedule itself
  25. scheduleExpirationRenewal(threadId);
  26. }
  27. }
  28. });
  29. }
  30. }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
  31. if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {
  32. task.cancel();
  33. }
  34. }

看一下lua脚本这段,其实很简单,就几行:

1.判断当前可以是否存在

2.存在的话重新设置超时时间

3.不存在则返回0

但是需要注意的是它执行周期是超时时间的1/3,比如设置30s超时,那么每隔10s他都会执行一次锁延时任务。

 


转载:https://blog.csdn.net/qq_38630810/article/details/106500951
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场