Redisson 分布式锁 - RLock、RReadWriteLock、RSemaphore、RCountDownLatch(配置、使用、原理)

本文主要是介绍Redisson 分布式锁 - RLock、RReadWriteLock、RSemaphore、RCountDownLatch(配置、使用、原理),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

前言

Redisson 分布式锁

环境配置

1)版本说明

2)依赖如下

3)配置文件如下

4)项目配置

RLock

1)使用方式

2)加锁解释

3)加锁时手动设置时间

4)加锁时,到底要不要手动设置过期时间?(最佳实践)

RReadWriteLock

1)使用方式

2)加锁原理

RSemaphore

1)使用方式

2)信号原理

RCountDownLatch

1)使用方式

2)原理解释


前言


前面讲过一篇 Redisson 分布式锁的底层原理,而这篇文章着重实战,因此对原理不清楚的,可以看看我之前的文章:http://t.csdnimg.cn/LU5ED

 

Redisson 分布式锁


环境配置

1)版本说明

  • SpringBoot:3.2.5
  • Redisson:3.25.0

 

2)依赖如下

<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-data-redis</artifactId>-->
<!--        </dependency>--><!--redisson 依赖整合了 StringRedisTemplate,因此 data redis 依赖就可以删除了--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.25.0</version></dependency>

 

3)配置文件如下

spring:  data:redis:host: env-baseport: 6379

Ps:实际上还有一种配置方式就是自己配置 RedissonClient 的 Bean,注入给容器

 

4)项目配置

分布式锁带来的效果演示,会通过 jmeter 来进行测试.  服务这边会先通过 网关,在负载均衡到集群的实例上.  因此这里我们来配置一下集群的每个实例信息.

这里为了方便观察,准备了两个实例:

RLock

1)使用方式

如果使用的 Redisson 的 Boot Starter 依赖的话,只需要在 yml 按照本文配置,然后在需要的地方注入 RedissionClient 即可使用(非 Boot Starter 依赖需要自己配置 RedissonClient).

@RestController 
@RequestMapping("/product/lock")
class Test(private val redisson: RedissonClient
) {@GetMapping("/test1")fun test1(): String {//1.获取一把锁,只要名字一样,就是同一把锁val lock: RLock = redisson.getLock("my-lock")//2.加锁lock.lock()try {println("加锁成功,执行业务..." + Thread.currentThread().id)Thread.sleep(10000) //模拟耗时任务} catch (e: Exception) {e.printStackTrace()} finally {//3.解锁lock.unlock()println("解锁成功!" + Thread.currentThread().id)}return  "ok!"}}

 Ps:除此之外,还有 redisson 的 ReentrantLock 中还提供了 tryLock() 方法有以下两种重载方式

  • boolean tryLock():尝试加锁,如果当前锁被占用,则直接放弃并返回 false.
  • boolean tryLock(long time, TimeUnit unit):如果当前锁被占用,则会等待,知道到达我们设置的过期时间 time 还没拿到锁,就放弃并返回 false.

 

2)加锁解释

a)RLock 就类似于 JUC 中的 ReentrantLock,是一个可重入锁(同一个线程对同一个资源连续加锁两次不会死锁).

b)加锁实际上就是在 redis 上添加了一个 key-value,并且默认加锁的过期时间为 30s,如下图:

c)如果业务处理实践比默认加锁时间长怎么办?这里会有一个看门狗机制,只要拿到锁,就会开启一个定时任务,每隔 10s 就会自动续约.  因此不用担心业务时间长的问题.  

d)如果代码还没有执行到解锁,程序就挂了,会不会死锁?不会的,锁是有默认的过期时间,即使没有执行到解锁逻辑,锁也会自动删除.(这里我自己测试了一下,貌似新版的 Redisson 中会检测程序是否挂了,如果挂了,就会把这个锁立即删除掉)

3)加锁时手动设置时间

a)使用如下:

b)注意:

如果我们手动指定了过期时间,那么即使业务没有执行完,也不会自动续约.  也就是说,无论如何,到期自动解锁.

4)加锁时,到底要不要手动设置过期时间?(最佳实践)

a)最佳实践:

使用 lock.lock(30, TimeUnit.SECONDS) 手动设置 30s 过期时间.  

b)原因:

如果我们手动设置过期时间,就省掉了续约的操作(有一定的开销).

再者,真的会有某一个业务逻辑需要执行 30s 的时间么?如果真的有,这个程序大概率是出问题了.  到了 30s 后解锁,反而还避免了 “死等” 问题.

RReadWriteLock

1)使用方式

a)写操作(写锁)

    @GetMapping("/write")fun write(): String {val rwLock: RReadWriteLock = redisson.getReadWriteLock("rw-lock")var result = ""//1.获取写锁val wLock = rwLock.writeLock()//2.写操作用写锁,读操作用读锁wLock.lock()try {Thread.sleep(10000)result = UUID.randomUUID().toString()redisTemplate.opsForValue().set("uuid", result)} catch (e: Exception) {e.printStackTrace()} finally {wLock.unlock()}return "ok! uuid: $result"}

b)读操作(读锁)

    @GetMapping("/read")fun read(): String {val rwLock: RReadWriteLock = redisson.getReadWriteLock("rw-lock")var result: String? = ""//1.获取读锁val rLock = rwLock.writeLock()//2.写操作用写锁,读操作用读锁rLock.lock()try {result = redisTemplate.opsForValue().get("uuid")} catch (e: Exception) {e.printStackTrace()} finally {rLock.unlock()}return "ok! uuid: $result"}

Ps:读锁,写锁 这里也额外提供了 tryLock() 方法,来尝试加锁(原理上面讲过)

2)加锁原理

读写锁保证了读操作和读操作之间不会加锁,而读操作和其他任何操作都会加锁.   使得在读多写少的业务场景中,效率大大提升.

Redission 提供的 ReadWriteLock 也是这个原理:

  • 读 + 读: 读操作和读操作之间不会出现脏数据问题,因此相当于无锁,只会在 redis 中记录当前读锁,他们都会同时加锁成功.
  • 写 + 读:如果先写,此时紧接着又进行读操作,可能出现脏数据的问题,因此会阻塞等待写锁释放.
  • 写 + 写:写操作和写操作之间可能出现脏数据问题,因此也是阻塞等待.
  • 读 + 写:由于你读的时候,另一个线程又来写,也会出现脏数据的问题,因此也必须要阻塞等待读锁释放.

RSemaphore

1)使用方式

    @GetMapping("/park")fun park(): String {//这里的 RSemaphore 就相当于是一个停车场,刚开始的没有车位(初始信号量为 0)val park: RSemaphore = redisson.getSemaphore("park")//1.获取一个信号,相当于占了一个停车位,车位 - 1park.acquire()//2.执行业务//...return "park ok!"}@GetMapping("/go")fun go(): String {val park = redisson.getSemaphore("park")//1.释放一个信号,相当于让出了一个车位,车位 + 1park.release()return "go ok!"}

Ps:这里也有一个额外的方法 tryAcquire(),尝试申请资源 

2)信号原理

a)redisson.getSemaphore("park") 这里实际上就是在 redis 上添加一个 key-value,key 就是我们自定义的 "park" 字符串,value 就是信号量,初始情况下为 0. 

b)情况分析:

情况一:刚开始的时候如果 线程A 进行 acquire(),由于信号量为 0,只能阻塞等待.  接着如果有 线程B 进行 release(),就会释放一个信号量,也就是信号量 + 1,此时 线程A 发现有一个信号来了,他就直接消费掉了

情况二:刚开始的时候如果 线程A 进行 release(),此时信号量 + 1,总共信号量为 1,接着如果有线程B 来进行 acquire() ,就会直接消费掉这个信号,此时信号量 - 1,总共信号量为 0.

Ps:信号量也可以做分布式限流

RCountDownLatch

1)使用方式

例如有 5 个选手比赛,要求所有选手到达终点之后才可以宣布比赛结束.

    @GetMapping("/match")fun match(): String {val match = redisson.getCountDownLatch("match")//1.设置计数器的初始值为 5 (想象成有 5 名选手赛跑)match.trySetCount(5)//2.等待所有资源全被消费 (等待 5 名选手全部跑完)match.await()return "比赛结束!"}@GetMapping("/gogogo/{id}")fun gogogo(@PathVariable("id") id: Long): String {val match = redisson.getCountDownLatch("match")//1.计数器 - 1 (一名选手到达终点)match.countDown()return "选手 $id 号到达终点!"}

2)原理解释

a)redisson.getCountDownLatch("match") 这里实际上就是给 redis 存了一个 key-value,key 就是我们自定义的 "match" 字符串,value 就是计数器.

b)match.trySetCount(5) 就是给这个计数器设置了一个初始值为 5.

c)match.await() 会一直阻塞住,直到计数器的值减为 0.

d)match.countDown() 让计数器 - 1.

这篇关于Redisson 分布式锁 - RLock、RReadWriteLock、RSemaphore、RCountDownLatch(配置、使用、原理)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1031709

相关文章

Zookeeper安装和配置说明

一、Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。 ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境; ■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例; ■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble) Zookeeper通过复制来实现

CentOS7安装配置mysql5.7 tar免安装版

一、CentOS7.4系统自带mariadb # 查看系统自带的Mariadb[root@localhost~]# rpm -qa|grep mariadbmariadb-libs-5.5.44-2.el7.centos.x86_64# 卸载系统自带的Mariadb[root@localhost ~]# rpm -e --nodeps mariadb-libs-5.5.44-2.el7

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

hadoop开启回收站配置

开启回收站功能,可以将删除的文件在不超时的情况下,恢复原数据,起到防止误删除、备份等作用。 开启回收站功能参数说明 (1)默认值fs.trash.interval = 0,0表示禁用回收站;其他值表示设置文件的存活时间。 (2)默认值fs.trash.checkpoint.interval = 0,检查回收站的间隔时间。如果该值为0,则该值设置和fs.trash.interval的参数值相等。

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传