文章目录

  • 一、分布式锁的概念 和 使用场景
  • 二、将redis官网对于分布式锁(红锁)的定义和Redisson实现做概括性总结
  • 三、基于Redisson的分布式实现方案
  • 四、加锁过程分析
  • 五、锁重入过程分析
  • 六、未获取到锁的线程继续获取锁
  • 七、锁释放过程分析
  • 八、易混淆概念

一、分布式锁的概念 和 使用场景

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。

  在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候,便需要使用到分布式锁。

二、将redis官网对于分布式锁(红锁)的定义和Redisson实现做概括性总结

  该部分可以先粗略的浏览一下,领略其官方的理论定义,读完后续内容会对该环节有更清晰的理解。

  对于Redis分布式锁(红锁)官网定义:

       中文对如上5点做出解释:

redis红锁算法:

  在Redis的分布式环境中,我们假设有N个Redis master。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。我们确保将在N个实例上使用与在Redis单实例下相同方法获取和释放锁。现在我们假设有5个Redis master节点,同时我们需要在5台服务器上面运行这些Redis实例,这样保证他们不会同时都宕掉。

  为了取到锁,客户端应该执行以下操作:

  • 1、获取当前时间,以毫秒为单位。
  • 2、依次尝试从5个实例,使用相同的key和随机值(Redisson中给出的是UUID + ThreadId)获取锁。当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间(我们接下来会在加锁的环节多次提到这个时间),这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在一直等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试去另外一个Redis实例请求获取锁。
  • 3、客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(N/2+1,这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
  • 4、如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
  • 5、如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。

针对如上几点,redisson的实现:

三、基于Redisson的分布式实现方案

  在分析Redisson的源码前,先重申一下我们本文的重点放在分布式锁的加锁、锁重入、未获取到锁的线程继续获取锁、释放锁四个过程!希望可以对大家有所帮助。

  锁重入:我们假设,一次加锁时间为30秒,当然Redisson默认的也是30秒,但是业务执行时间大于30秒,如果没有锁重入的实现,那么30秒后锁失效,业务逻辑就会陷入无法保证正确性的严重后果中。

第一步:添加依赖

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

在正式编码前,我们先看下有关Redisson实现分布式锁的核心类之间的关系,如下图:

第二步:正式编码测试代码

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootIntegrationComponentApplication.class)
public class ReidsRedLockTest { private ExecutorService executorService = Executors.newCachedThreadPool(); public RedissonRedLock getRedLock(){
Config config1 = new Config();
config1.useClusterServers()
.addNodeAddress("redis://127.0.0.1:9001","redis://127.0.0.1:9002","redis://127.0.0.1:9003"
,"redis://127.0.0.1:9004","redis://127.0.0.1:9005","redis://127.0.0.1:9006")
.setPassword("123");
RedissonClient redissonClient1 = Redisson.create(config1);//创建redissonClient对象,设置一系列的redis参数
RLock rLock1 = redissonClient1.getLock("red_lock");
//如果有多个redis cluster集群,则参考如上的写法创建对应的RLock对象,并传入下面的RedissonRedLock构造方法中。
return new RedissonRedLock(rLock1);//获取redisson红锁
} @Test
public void redisRedLock() throws Exception {
RedissonRedLock redLock = getRedLock(); int[] count = {0};
for (int i = 0; i < 1000; i++) {
executorService.submit(() -> {
try {
redLock.tryLock(10, TimeUnit.SECONDS);//加锁
count[0]++;
Thread.sleep(50000L);
} catch (Exception e) {
log.error("添加分布式锁异常:",e);
} finally {
try {
redLock.unlock();//释放锁
} catch (Exception e) {
log.error("解除分布式锁异常:",e);
}
}
});
}
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.HOURS);
log.info("计算后的结果:{}",count[0]);
}
}

四、加锁过程分析

首先我们将加锁过程的方法调用栈列出,按照调用步骤分析加锁的源码实现:

由上述调用栈可以看到,实现加锁的核心方法是:

这是一个调用lua脚本的执行过程,接下来对该方法做详细解释:

针对lua脚本中参数占位符的问题:

  • KEYS[1] = getName(),
  • ARGV[1] = internalLockLeaseTime
  • ARGV[2] = getLockName(threadId)

针对getLockName(threadId)方法,在创建redis连接管理器时,设置了id = UUID,具体如下

  我们假设线程A,执行完上面的lua脚本,并且持有了该分布式锁,接下来针对线程A来说,直到业务逻辑结束,释放锁之前,该线程A,都将进入锁重入的环节,一直持续到业务逻辑执行完成,线程主动释放锁。而没有持有锁的线程,则进入争抢锁的过程,一直到持有锁(至于是公平竞争还是非公平竞争,我们先留一个悬念,欢迎各位看官老爷在评论区留言讨论)。

五、锁重入过程分析

再让我们回到加锁过程中方法调用栈的图片上,我们可以看到方法:

上图中的红框即是锁重入的实现方法,详细解释如下:

同样是利用lua脚本实现,

具体逻辑为:

  • 0、我们假设线程A持有了该锁,则后台线程会在该锁持续了初始失效时间除3取整数的时间节点,做锁重入的操作。
  • 1、if判断指定的key是否存在,且是否为当前线程所持有
  • 2、如果被当前线程持有,则将失效时间重置为初始失效时间,redisson默认为30秒。
  • 3、如果上面两步操作成功,则返回1,也即是true;否则返回false。

六、未获取到锁的线程继续获取锁

让我们将思路继续回到线程A获取锁的逻辑中,我们通过加锁方法调用栈可以看到方法:

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException 

该方法实属有些长,我们就分段截取分析。

 

  通过上图的分析,我们知道,如果一个线程初次没有获取到锁,则会一直尝试获取锁,直到我们设置的针对获取该redis实例锁的超时时间耗尽才罢休,在这个过程中没有获取到锁,则认为在该redis实例获取锁失败。

七、锁释放过程分析

我们还是先将锁释放过程方法调用栈列出:

 

通过上图可以看到,在锁释放的过程中,最核心的方法就是:

分析其lua脚本实现逻辑:

  分析可知,在删除对应的key之后,会发布一条消息以供其他未获取到锁的线程订阅,此逻辑和加锁过程遥相呼应,并且在删除key之后做了移除锁重入资格的操作,以保证当前线程彻底释放锁。

八、易混淆概念

我们所说的一个redis实例,并不是一个Redis集群中的某一个master节点或者Slave节点,针对redis集群,一个集群在redLock算法中只是一个实例节点,至于我们的key值放在了哪个slot,是由Redis集群的一致性算法决定的。同样对于哨兵模式也是这样。所以针对RedLock算法来说,如果有N个实例,则是指N个cluster集群、N个sentinel集群、N个redis单实例节点。而不是一个集群中的N个实例。

基于Redisson实现分布式锁源码解读的更多相关文章

  1. Redisson 分布式锁源码 09:RedLock 红锁的故事

    前言 RedLock 红锁,是分布式锁中必须要了解的一个概念. 所以本文会先介绍什么是 RedLock,当大家对 RedLock 有一个基本的了解.然后再看 Redisson 中是如何实现 RedLo ...

  2. Redisson 分布式锁源码 02:看门狗

    前言 说起 Redisson,比较耳熟能详的就是这个看门狗(Watchdog)机制. 本文就一起看看加锁成功之后的看门狗(Watchdog)是如何实现的? 加锁成功 在前一篇文章中介绍了可重入锁加锁的 ...

  3. Redisson 分布式锁源码 11:Semaphore 和 CountDownLatch

    前言 Redisson 除了提供了分布式锁之外,还额外提供了同步组件,Semaphore 和 CountDownLatch. Semaphore 意思就是在分布式场景下,只有 3 个凭证,也就意味着同 ...

  4. RedissonLock分布式锁源码分析

    最近碰到的一个问题,Java代码中写了一个定时器,分布式部署的时候,多台同时执行的话就会出现重复的数据,为了避免这种情况,之前是通过在配置文件里写上可以执行这段代码的IP,代码中判断如果跟这个IP相等 ...

  5. Redisson 分布式锁源码 01:可重入锁加锁

    前言 相信小伙伴都是使用分布式服务,那一定绕不开分布式服务中数据并发更新问题! 单系统很容易想到 Java 的各种锁,像 synchronize.ReentrantLock 等等等,那分布式系统如何处 ...

  6. 【连载】redis库存操作,分布式锁的四种实现方式[二]--基于Redisson实现分布式锁

    一.redisson介绍 redisson实现了分布式和可扩展的java数据结构,支持的数据结构有:List, Set, Map, Queue, SortedSet, ConcureentMap, L ...

  7. 基于Redisson实现分布式锁

    前言 最近开发了几个微服务上线了,发现定时任务执行了很多次,查看rancher发现这几个微服务都是多实例的,也就是说定时任务执行了多次,恰好所用框架中使用的是Redisson, 正好记录下使用Redi ...

  8. Java集合(四)--基于JDK1.8的ArrayList源码解读

    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess ...

  9. 基于redis的分布式锁实现方案--redisson

    实例代码地址,请前往:https://gitee.com/GuoqingLee/distributed-seckill redis官方文档地址,请前往:http://www.redis.cn/topi ...

随机推荐

  1. Zab协议 (史上最全)

    文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...

  2. 你能说出多线程中sleep、yield、join的用法及sleep与wait区别?

    Object中的wait.notify.notifyAll,可以用于线程间的通信,核心原理为借助于监视器的入口集与等待集逻辑 通过这三个方法完成线程在指定锁(监视器)上的等待与唤醒,这三个方法是以锁( ...

  3. 【题解】Luogu P2214 [USACO14MAR]哞哞哞Mooo Moo

    P2214 [USACO14MAR]哞哞哞Mooo Moo 题目描述 Farmer John has completely forgotten how many cows he owns! He is ...

  4. StringUtils中的常量

    //空格字符串 public static final String SPACE = " "; //空字符串 public static final String EMPTY = ...

  5. es6快速入门 系列 - async

    其他章节请看: es6 快速入门 系列 async 前文我们已经知道 promise 是一种异步编程的选择.而 async 是一种用于执行异步任务更简单的语法. Tip:建议学完 Promise 在看 ...

  6. Windows10 上Docker 安装运行Consul

    背景简介 Consul是一种服务网格解决方案,提供具有服务发现,配置和分段功能的全功能控制平面. 这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全服务网格. Consul需要数据平面并 ...

  7. centos 8 sonarqube 设置开机自启动解决方案

    编写开机自启动脚本 进入到开机自启动调用文件目录 vim /etc/systemd/system/sonarqube.service [Unit] Description=SonarQube serv ...

  8. Https:证书生成 .p12 .keyStore 和 .truststore文件理解

    当我们需要SSL证书时,可以自动生成SSL证书,但是每个系统都申请一次证书会比较麻烦,所以用到了如下几个文件格式:   .p12(PKCS #12) 我们的每一个证书都可以生成一个.p12文件,这个文 ...

  9. leetcode 面试题 17.22. 单词转换(DFS+回溯)

    题目描述 思路分析 这题回溯,先想出它的空间解是什么,这里空间解,其实就是给的原字符串到结束字符串中间的变形过程,那么就可以容易的画出一个解空间树,用深度搜索进行搜索, 剪枝后,进入下一个维度,再进行 ...

  10. 简单学习java内存马

    看了雷石的内存马深入浅出,就心血来潮看了看,由于本人java贼菜就不介绍原理了,本文有关知识都贴链接吧 前置知识 本次主要看的是tomcat的内存马,所以前置知识有下列 1.tomcat结构,tomc ...