持续创造,加速成长!这是我参与「日新方案 10 月更文应战」的第5天,点击查看活动概况

上一篇文章:用万字长文来讲讲本地锁至分布式锁的演进和Redis完结

在上一篇中挖了这个坑,今日就来把它填上哈~ ? 昨日聊到了Redisson完结了锁的主动续期,可是就简略提了一嘴就完事了。

今日就来多说一点点里面的完结和一些运用,它和咱们自己的完结比较又怎么。

文章大纲:

榜首部分说了 Redisson简略运用

第二部分才是说Redisson底层源码怎么完结分布式锁

1、 怎么加锁

2、 怎么完结锁主动续期,靠什么完结的?

3、 怎么完结解锁

一、Redisson 简略运用

SpringBoot 中,因为主动安装的存在,运用某个封装好的轮子,就那么几步~

  • 导包
  • 编写装备
  • 编写xxxConfig
  • 预备开端运用它

1.1、导包

Redisson 也不破例

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson-spring-boot-starter</artifactId>
  <version>3.17.6</version>
</dependency>

1.2、装备

这儿的装备便是Redis的装备

spring:
  redis:
   host: IP地址
   password: xxxx

1.3、编写装备类

那些什么最大衔接数,衔接超时等等就没配了,偷个懒~

@Configuration
public class MyRedissonConfig {
​
  /**
   * 一切对Redisson的运用都是经过RedissonClient
   * @return
   * @throws IOException
   */
  @Bean(destroyMethod="shutdown")
  public RedissonClient redissonClient() throws IOException {
    //1、创立装备
    Config config = new Config();
    config.useSingleServer().setAddress("redis://4IP地址:6379").setPassword("xxxx");
//    config.setLockWatchdogTimeout();
    //2、依据Config创立出RedissonClient实例
    //Redis url should start with redis:// or rediss://
    RedissonClient redissonClient = Redisson.create(config);
    return redissonClient;
   }
}

注意:这儿面的setAddress()中的地址有必要以redis://最初~

别的我这儿只是设置了这么几个属性,许多我没去写了罢了,不是它不能设置哈。

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

就比方修正锁主动需求的默许时刻,便是

config.setLockWatchdogTimeout(); 锁主动续期默许时刻是30s,这是能够被修正的。

其他的还需靠咱们自己探索啦~

1.4、简略上手

最直接的运用:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {主启动类.class})
public class RedisTest {
​
  @Autowired
  private RedissonClient redissonClient;
​
  @Test
  public void testRedisson() {
    // 获取锁实例
    RLock lock = redissonClient.getLock("redisson:lock");
    try {
      lock.lock();
      //履行需求加锁的事务~ 
     } finally {
      lock.unlock();
     }
   }
}

这个lock()办法,它是能够填写参数,也能够不填的,

public void lock() 
public void lock(long leaseTime, TimeUnit unit) 

不填写参数,Redisson它帮助咱们完结了锁的主动续期,

假如咱们自己填写了锁的时刻,Redisson则不会帮咱们完结锁的主动续期。

这一点在后面剖析源码的有详细的阐明。

别的Redisson它作为分布式锁的完结,更好的便利Java开发者,它完结了JavaJUC包下的诸多接口,能够说运用起来彻底没啥学习成本~

比方JUC下的读写锁,

@Test
public void testRedissonReadAndWriteLock() {
  RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("redisson:lock");= redissonClient.getReadWriteLock("redisson:lock");
  // 获取写锁
  RLock writeLock = readWriteLock.writeLock();
  // 获取读锁
  RLock readLock = readWriteLock.readLock();
  try {
    //上锁
    writeLock.lock();
    // 这种办法,运用咱们自己界说的
    writeLock.lock(10,TimeUnit.SECONDS);
    //履行需求加锁的事务~
   } finally {
    writeLock.unlock();
   }
}

还有信号量RSemaphore semaphore = redissonClient.getSemaphore("semaphore");

更有其他的不少:

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

详细的案例,其实在它的文档中都有阐明的,有中文版别的,很简略的~

官方文档

这些该怎么运用,其实就和用JUC下面的工具相同,懂了那一块的知识,就知道这儿该怎么运用啦,不懂的话,我说了也还是相同的哈哈~

让我偷个懒,能够去学一学JUC,蛮好玩的~

二、锁主动续期源码-看门狗机制剖析

在剖析之前,咱们先把上一篇中分布式锁演进进程中所发生的问题都逐个抛出来,之后再针对性的看看 Redisson 它又是怎么处理的。

2.1、分布式锁演进的问题

为了便利其余没有看过看一篇文章的朋友,我把分布式锁中存在的几个问题,递推式的抽离出来了。

咱们都知道要正确运用分布式锁,一定要注意原子性操作、死锁问题和被他人开释锁的问题和锁是否需求主动续期问题

  1. 死锁问题:说的是锁没有被动过期时刻,即拿到了锁,可是在履行事务进程中,程序崩溃了,锁没有被正常开释,导致其他线程无法获取到锁,从而发生死锁问题。

    之前的处理办法:set nx ex指令

  2. 锁被其他人开释问题:榜首条线程抢到锁,事务履行超时,榜首条线程所持有的锁被主动开释;此刻第二条线程拿到锁,预备履行事务,刚好榜首条线程事务履行完结,照常履行了开释锁的进程,导致第二条线程持有的锁被榜首条线程所开释,锁被其他人开释

    之前的处理办法:给锁一个仅有值(UUID),每次解锁前进行判别

  3. 原子性操作:原子性的意思便是要么都成功,要么都失败。像咱们获取锁,设定值和设守时刻是两步操作,让他们变成原子性操作便是设定值和设守时刻成为一体,一同成功或许一同失败。别的解锁操作的获取锁,判别锁是否为当时线程所具有,也有必要是一个原子性操作。

    之前的处理办法:运用Redis中的lua脚本完结。

  4. 锁主动续期问题:这个便是运用了咱们今日的Redisson来完结的。

2.2、剖析的初步

咱们就以最简略的流程

@Test
public void testRedisson() {
  RLock lock = redissonClient.getLock("redisson:lock");
  try {
    lock.lock();
    //履行需求加锁的事务~
   } finally {
    lock.unlock();
   }
}

来剖析以下几个问题:

  1. Redisson 是怎么完结加锁的
  2. Redisson 是怎么完结锁主动续期的
  3. Redisson 是怎么解锁的

以及在这个进程中,去看看 Redisson它是怎么处理咱们之前呈现的问题的。

顺着看下去~

RLock lock = redissonClient.getLock("redisson:lock");
lock.lock();

看似加锁操作只调用了一个lock()办法,但实际上流程走的可多了~

不多说,直接在Idea中点击跳转到RedissonLock中的类中

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

弥补阐明:这下面还有一些完结自旋的操作,便是写了一个while(true)来完结等候获取锁的代码,没有持续往下剖析了。中间还有一些判别锁是否能够被打断、订阅和退订等操作,没有去细心研究啦,咱们感兴趣能够再往下多看看。


接着往下走

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

2.3、Redisson 怎么完结加锁-tryLockInnerAsync()

漏剖析了一步,不是从scheduleExpirationRenewal(threadId)前进到下一步,这说的是锁续期问题~

忘掉说加锁啦~ 加锁便是tryLockInnerAsync()办法

点进去看看~

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

弥补阐明:

在这儿能够看到几个方面:Redisson底层也是运用Lua脚原本完结的,这段 lua 脚本分为三个部分:

  1. 榜首部分:加锁

    首要用 exists 判别了 KEYS[1] (即 redisson:lock)是否存在。 假如不存在,则进入第 5 行,运用 hincrby 指令创立一个新的哈希表,假如域field不存在,那么在履行指令前会被初始化为0,此指令的回来值便是履行hincrby指令后,哈希表key中域field的值,此刻进行increment,也便是回来1,之后进入第6行,对KEY[1]设置过期时刻,30000ms然后回来nil。

  2. 第二部分:重入

    首要判别KEY[1]是否存在,因为KEY[1]是一个hash结构,所以13行意思是获取这个KEYS[1]中字段为ARGV[2]也便是UUID:thredId这个值是否存在。

    假如存在进入14行代码对其进行加1操作(锁重入) 然后进入15行从头设置过期时刻30s 然后回来nil

  3. 第三部分:回来:作用便是回来 KEY[1] 的剩余存活时刻

此处的getRawName()办法便是咱们获取到咱们设定的锁名。如此处便是获取到Redisson:lock

getLockName(threadId)便是获取一个UUID:threadId的字符

串。

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

至于说此处的UUID,是怎么来的,我稍微浅看了一下,这是初始化的时分给每个衔接管理器都传了一个UUID的类,更详细的运用,没去追啦。

咱们知道这是 Redisson 也是采取相同的办法,它的值也是存了一个UUID,这点同样也能够在衔接工具上查看到的。

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

看到这儿就现已能够看出Redisson现已处理咱们昨日呈现的悉数问题了。

首要是加锁操作的原子性是满足的了,因为 Redisson运用的Lua脚本将设置值和设置时刻的操作变为一步;其次是之前的锁被其他人开释的问题,在这儿Redisson也选用了仅有keyUUID来处理此问题。

那么剩下的就只有锁续期问题了,咱们接着往下看

2.4、Redisson 怎么完结锁续期

从这个scheduleExpirationRenewal(threadId);办法开端持续往下探索~

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

咱们回过头来接着看 续期办法renewExpiration()

看它底层是怎么完结的。

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

能够从这段代码中很明显的看出,是启动了一个守时使命,该使命每 internalLockLeaseTime/3ms 后履行一次。而 internalLockLeaseTime默许为 30000。所以该使命每 10s 履行一次。


renewExpirationAsync(long threadId)办法,便是完结锁从头续期的lua脚本的履行

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

上面的守时使命在不修正看门狗的默许时刻时,便是每10s履行一次,意思便是每次在锁还剩下20s时,就会履行这段从头续期的代码,让锁从头续期到30s。

不知道提到这儿,大伙有听懂吗~

2.5、Redisson 怎么完结解锁操作

其实看懂了Redisson是怎么加锁的,其实看这解锁操作也是特别简单的。

不过咱们这次要回到开端剖析的地方去啦。

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

看的出来,解锁操作并不复杂,咱们先去看看unlockInnerAsync(threadId);办法吧。

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

其实看完加锁,再看这个其实都能理解啦吧

  • 首要判别KEYS[1]是否存在
  • 存在将值减1,假如counter还大于0,就从头设置过期时刻30000ms,不然就删去操作
  • redis.call('publish', KEYS[2], ARGV[1]);同时发布了一个事件,这个是干嘛的?是去通知正在等候获取锁其他的线程们,能够运用这把锁了。

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

别的cancelExpirationRenewal(threadId);办法便是一些取消和删去操作。

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

总结

Redisson 比较较咱们自己的完结怎么?

首要能够说的是,这个轮子考虑的比咱们周到的多,在上篇说的各种问题都是处理了的。

在各种加锁或许解锁操作上都完结了原子性~

几个点:

  1. 在运用 Redisson 获取锁的进程,你主动设定了锁的过期时刻,`Redisson 将不会敞开看门狗机制。
  2. Redisson 在 Redis 中保存的结构是一个 Hash的数据结构,key 的称号是咱们的锁称号,如案例中运用的 redisson:lock,存储的字段称号为 UUID:threadId,值的话便是 1
  3. 结构如下图:
  4. Redisson完结锁主动续期的底层是敞开了一个线程,异步的履行守时使命,在锁还剩下20s,主动续期为30s,此守时使命是选用Netty结构中的时刻轮算法完结。

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析
今日就看到这儿啦,周四挖的坑,周五熬夜总算写出来了。

跋文

不知道这篇文章有没有帮助到你,希望看完的你,关于Redisson现已没有那么惊骇~,当然它其实也不难。

假如觉得有收成的话,记得点点赞,给我来个关注吧~

周四埋下的坑,周五来恶补!! Redisson 加锁、锁自动续期、解锁源码分析

写于 2022 年 10 月 21 日晚,作者:宁在春

今日是好累的一天啊~