【SpringBoot】Redission 的使用与介绍

背景:

我想我们用到 Redisson 最多的场景一定是分布式锁,一个基础的分布式锁具有三个特性:

互斥:在分布式高并发的条件下,需要保证,同一时刻只有有一个线程获得锁,这是最基本的一点。

防止死锁:在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来的及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。

可重入:我们知道 ReentrantLock 是可重入锁,那它的特点就是同一个线程可以重复拿到同一个资源的锁。

一、SpringBoot 应用

1、通过 Maven 引入依赖

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.14.0</version>
</dependency>

2、在 yml 文件中添加配置

spring:redis:host: 127.0.0.1port: 6379timeout: 10000password:lettuce:pool:#最大连接数据库连接数,设 0 为没有限制max-active: 8#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。max-wait: 1000#最大等待连接中的数量,设 0 为没有限制max-idle: 500#最小等待连接中的数量,设 0 为没有限制min-idle: 300jedis:pool:max-active: 8max-wait: 1000max-idle: 500min-idle: 300

3、自定义配置类

@Configuration
public class RedissonConfig {@Value("${spring.redis.password}")private String password;@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private String port;/*** 对 Redisson 的使用都是通过 RedissonClient 对象* @return*/   @Bean(name = "redissonClient", destroyMethod = "shutdown") // 服务停止后调用 shutdown 方法public RedissonClient redissonClient() {// 1、创建配置Config config = new Config();// 2、集群模式// config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");// 根据 Config 创建出 RedissonClient 示例config.useSingleServer().setPassword(StringUtils.isEmpty(password) ? null : password).setAddress(host.contains("://") ? "" : "redis://" + host + ":" + port);return Redisson.create(config);}}

4、测试配置类

    @Autowiredprivate RedissonClient redissonClient;@Testpublic void test() {System.out.println(redissonClient); // org.redisson.Redisson@40a8a26f}

二、使用 Redisson 分布式锁

用锁的一般步骤:

  1. 获取锁实例(只是获得一把锁的引用,并不是占有锁)
  2. 通过锁实例加锁(占有了这把锁)
  3. 通过锁实例释放锁

Redisson 提供很多种类型的锁,其中最常用的就是可重入锁(Reentrant Lock)了。

Redisson 中的可重入锁

1、获取锁实例

RLock lock = redissonClient.getLock(String lockName);

获取的锁实例实现了 RLock 接口,而该接口拓展了 JUC 包中的 Lock 接口,以及异步锁接口 RLockAsync。

2、通过锁实例加锁(Rlock 常用的方法)

同步异步特性来区分,加锁方法可分为同步加锁和异步加锁两类。异步加锁方法的名称一般是在相应的同步加锁方法后加上“Async”后缀。
阻塞非阻塞特性来区分,加锁方法可分为阻塞加锁和非阻塞加锁两类。非阻塞加锁方法的名称一般是“try”开头。

下面以比较常用的同步加锁方法来说明加锁的一些细节。

阻塞加锁的方法:
  • void lock():lock 表示如果当前锁可用,则加锁成功,并立即返回,没有返回值,继续执行下面代码;如果当前锁不可用,则阻塞等待直至锁可用(当前锁失效时间默认30 s),然后返回。
//创建锁
RLock helloLock = redissonClient.getLock("hello");//加锁
helloLock.lock();
try {log.info("locked");Thread.sleep(1000 * 10);
} finally {//释放锁helloLock.unlock();
}
log.info("finished");
  • void lock(long leaseTime, TimeUnit unit):加锁机制与 void lock() 相同,只是增加了锁的有效(租赁)时长 leaseTime。加锁成功后,可以在程序中显式调用 unlock() 方法进行释放;如果未显式释放,则经过 leaseTime 时间,该锁会自动释放。如果 leaseTime 传入 -1,则会开启看门狗机制,跟上面 lock() 方法意义一致。
非阻塞加锁的方法:
  • boolean tryLock():(JUC 中 Lock 接口定义的方法)调用该方法会立刻返回。返回值为true则表示锁可用,加锁成功;返回值为false则表示锁不可用,加锁失败。
  • boolean tryLock(long time, TimeUnit unit):尝试去加锁(第一个参数表示 the maximum time to wait for the lock),如果锁可用则立刻返回 true,继续执行 true 下面代码,否则最多等待 time 长的时间(如果 time<=0,则不会等待)。在 time 时间内锁可用则立刻返回 true,time 时间之后返回 false。如果在等待期间线程被其他线程中断,则会抛出  InterruptedException 异常。
String key ="product:001";
RLock lock = redisson.getLock(key);
try {boolean res = lock.tryLock(10,TimeUnit.SECONDS);if ( res){System.out.println("这里是你的业务代码");} else {System.out.println("系统繁忙");}
} catch (Exception e){e.printStackTrace();
} finally {lock.unlock();
}
  • boolean tryLock(long waitTime, long leaseTime, TimeUnit unit):与 boolean tryLock(long time, TimeUnit unit) 类似,只是增加了锁的使用(租赁)时长 leaseTime。表示尝试去加锁(第一个参数表示等待时间,第二个参数表示 key 的失效时间),加锁成功,返回true,继续执行 true 下面代码。如果返回 false,它会等待第一个参数设置的时间,然后去执行 false 下面的代码。
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);

解读:尝试加锁,最多等待 100 秒,上锁以后 10 s自动解锁,没有 watch dog 机制。

3、通过锁实例释放锁

void unlock():释放锁。如果当前线程是锁的持有者(即在该锁实例上加锁成功的线程),则会释放成功,否则会抛出异常。

4、异步执行分布式锁

/**
* 异步锁
*/
lock = redissonClient.getLock("erbadagang-lock");
Future<Boolean> res = null;
try {// lock.lockAsync();// lock.lockAsync(100, TimeUnit.SECONDS);res = lock.tryLockAsync(3, 100, TimeUnit.SECONDS);if (res.get()) {System.out.println("这里是你的Async业务代码");} else {System.out.println("系统繁忙Async");}
} catch (Exception e) {e.printStackTrace();
} finally {if (res.get()) {lock.unlock();}
}log.info("finished");

三、一般编程范式

1、同步阻塞加锁

String lockName = ...
RLock lock = redissonClient.getLock(lockName);
// 阻塞式加锁
lock.lock();
try {// 操作受锁保护的资源} finally {// 释放锁lock.unlock();
}

2、同步非阻塞加锁

String lockName = ...
RLock lock = redissonClient.getLock(lockName);
if (lock.tryLock()) {try {// 操作受锁保护的资源} finally {lock.unlock();}
} else {// 执行其他业务操作
}

四、分布式锁分析

优秀的分布式锁需要具备以下特性:

  • 互斥性:在任意时刻,只有一个客户端(线程)能持有锁,这是锁的基本要求。
  • 锁的可重入:同一个客户端能多次持有同一把锁。实现上只要检查锁的持有者是否为当前客户端,若是则重入锁成功,并将锁的持有数加1。一般通过给每个客户端分配一个唯一的ID,并在加锁成功时向锁中写入该ID即可。
  • 不会因客户端异常而长久锁住:当客户端在持有锁期间崩溃而未主动解锁时,锁也会在一定时间后自动释放,即锁有超时自动释放的特性。
  • 解锁的安全性:加锁和解锁必须是同一个客户端,客户端不能把别人加的锁给释放了,即不能误解锁。实现上与锁的可重入类似,在释放锁时检查客户端ID与锁中保存的ID是否一致即可。

Redisson的分布式锁除了实现上述几个特性外,还具有锁的自动续期功能。即当我们加锁而未指定锁的有效时长时,Redisson会按一定的周期,定时检查当前线程是否活跃,若是则自动为锁续期,这一特性称为watchdog(看门狗)机制。
有了这个特性,我们就可以不必为设定锁的有效时间而纠结了(设得太长,则会在客户端崩溃后仍长时间占有锁;设得太短,则可能在业务逻辑执行完成前,锁自动释放),Redisson分布式锁可以在客户端崩坏时自动释放,业务逻辑未执行完时自动续期。

五、看门狗机制

// 拿锁失败时,会不停的重试
RLock lock = redissonClient.getLock("guodong");
// 具有 watch Dog 自动延期机制,默认续 30 s
lock.lock();// 尝试拿锁10s后停止重试,返回false,具有 watch dog 自动延期机制默认续30s
boolean res1 = lock.tryLock(10, TimeUnit.SECONDS); 
// 没有 watch dog,10s后自动释放
lock.lock(10, TimeUnit.SECONDS);
// 尝试拿锁100s后停止重试,返回false,没有 watch Dog,10s后自动释放
boolean res2 = lock.tryLock(100, 10, TimeUnit.SECONDS);
Thread.sleep(40000L);
lock.unlock();

 5.1 Watch Dog 的自动延期机制

如果拿到分布式锁的节点宕机,且这个锁正好处于锁住的状态时,会出现锁死的状态,为了避免这种情况的发生,锁都会设置一个过期时间。这样也存在一个问题,加入一个线程拿到了锁设置了30s超时,在30s后这个线程还没有执行完毕,锁超时释放了,就会导致问题,Redisson 给出了自己的答案,就是 watch dog 自动延期机制。

Redisson 提供了一个监控锁的看门狗,它的作用是在 Redisson实例被关闭前,不断地延长锁的有效期,也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断地延长锁超时时间,锁不会因为超时而被释放。

默认情况下,看门狗的续期时间是30s,也可以通过修改 Config.lockWatchdogTimeout 来另行指定。另外 Redisson 还提供了可以指定 leaseTime 参数的加锁方法来指定加锁的时间。超过这个时间后锁便自动解开了,不会延长锁的有效期。

5.2 结论

  • watch dog 只有在未显示指定加锁时间(leaseTime)时才会生效。(这点很重要)
  • watch dog 如果指定加锁时间(leaseTime = -1),也是会开启看门狗机制的
  • watch dog 在当前节点存活时每 10 s给分布式锁的 key 续期 30s
  • watch dog 机制启动,且代码中没有释放锁操作时,watch dog 会不断地给锁续期
  • 如果程序释放锁操作时因为异常没有被执行,那么锁无法被释放,所以释放锁操作一定要放到 finally {} 中
  • 要使 watch dog 机制生效,lock 时不要设置过期时间
  • watch dog 的延时时间可以由 lockWatchDogTimeout 指定默认延时时间,但是不要设置太小
  • watch dog 会每 lockWatchDogTimeout / 3 时间去延时
  • watch dog 通过类似 netty 的 Future 功能来实现异步延时
  • watch dog 最终还是通过 lua 脚本来进行延时

五、参考文档

  • Redisson分布式锁在Java应用中的使用
  • Spring Boot中使用Redisson

  • redisson中的看门狗机制总结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/155711.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于知识问答的上下文学习中的代码风格11.20

基于知识问答的上下文学习中的代码风格 摘要1 引言2 相关工作3 方法3.1 概述3.2 元函数设计3.3 推理 4 实验4.1 实验设置4.2 实施细节4.3 主要结果 摘要 现有的基于知识的问题分类方法通常依赖于复杂的训练技术和模型框架&#xff0c;在实际应用中存在诸多局限性。最近&#x…

泵类设备常见的5种故障及监测方法

在各种工业领域中&#xff0c;泵是一种关键设备&#xff0c;用于输送液体或气体。然而&#xff0c;泵类设备常常会面临各种故障&#xff0c;这可能导致生产停顿和生产效率下降。为了及时监测并解决这些故障&#xff0c;设备状态监测系统成为一种重要的工具。本文将介绍泵类设备…

静态工具类中注入Bean及引用Nacos配置

目录 1.说明 2.示例 1.说明 在代码开发中&#xff0c;经常会存在调用第三方工具或者其他系统的场景&#xff0c;通常封装成一个工具类供service进行调用&#xff0c;便于后期的维护及代码复用。工具类中的属性及方法都被static修饰&#xff0c;在工具类中不能使用和service中…

Pytorch torch.norm函数详解用法

torch.norm参数定义 torch版本1.6 def norm(input, p"fro", dimNone, keepdimFalse, outNone, dtypeNone)input input (Tensor): the input tensor 输入为tensorp p (int, float, inf, -inf, fro, nuc, optional): the order of norm. Default: froThe following …

零代码编程:用ChatGPT将SRT字幕文件批量转为Word文本文档

一个文件夹中有多个srt视频字幕文件&#xff0c;srt文件里面有很多时间轴&#xff1a; 现在想将其批量转为word文档&#xff0c;去掉里面与字符无关的时间轴&#xff0c;在ChatGPT中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个批量将SRT字幕文件转为…

js 数组中使用 push 报错

文章目录 问题分析 问题 代码如下&#xff0c;但报错如上&#xff0c;请分析上述代码错误原因 let arr [[160, 20], [179, 10], [-170, -20]]; let temp arr.unshift([1,1]); let tt temp.push([0,0])console.log(tt); // 输出新生成的数组分析 这段代码有几个错误&#x…

opencv-形态学处理

通过阈值化分割可以得到二值图&#xff0c;但往往会出现图像中物体形态不完整&#xff0c;变的残缺&#xff0c;可以通过形态学处理&#xff0c;使其变得丰满&#xff0c;或者去除掉多余的像素。常用的形态学处理算法包括&#xff1a;腐蚀&#xff0c;膨胀&#xff0c;开运算&a…

CDN加速在目前网络安全里的重要性

在当今数字化时代&#xff0c;网络已经成为我们生活不可或缺的一部分。然而&#xff0c;随之而来的是网络安全的威胁不断增加&#xff0c;为了应对这一挑战&#xff0c;CDN&#xff08;内容分发网络&#xff09;的应用变得愈发重要。本文将从一个旁观者的角度分析当前的网络安全…

【设计模式】结构型设计模式

结构型设计模式 文章目录 结构型设计模式一、概述二、适配器模式&#xff08;Adapter Pattern&#xff09;2.1 类适配器模式2.2 对象适配器模式2.3 接口适配器模式2.4 小结 三、桥接模式&#xff08;Bridge Pattern&#xff09;四、装饰器模式&#xff08;Decorator Pattern&am…

基于ubuntu20.04安装ros系统搭配使用工业相机

基于ubuntu20.04安装ros系统搭配使用工业相机 1. ROS系统安装部署1.1更新镜像源1.1.1 备份源文件1.1.2 更新阿里源1.1.3 更新软件源 1.2 ros系统安装1.2.1 添加ros软件源1.2.2 添加秘钥1.2.3 更新软件源1.2.4 配置及更换最佳软件源1.2.5 ROS安装1.2.6 初始化rosdep1.2.7 设置环…

datagrip只导出表结构

话不多说&#xff0c;直接上教程。 datagrip版本&#xff1a;2022.3 第一步&#xff0c;连接数据库 第二步&#xff0c;右击数据库&#xff0c;复制即可

【游记】NOIP2023

Day -8 本来以为初中生不能参加NOIP。 老师突然通知&#xff0c;说初中生参加NOIP的分数线定在140分&#xff0c;我可以进NOIP&#xff0c;我开心了 &#x1f603; 于是我又开始备(juan)战(ti)了。 Day -8、-7、-6、-5 晚上9:00-10:00卷题。 卷了一些往年NOIP的水题。。。 Da…

【20年扬大真题】编写对数组求逆的递归算法

【20年扬大真题】 编写对数组求逆的递归算法 void swap(int* a, int* b) {int tmp *b;*b *a;*a tmp; } void Ni(int arr[],int left,int right) {if (left > right) {return;}swap(&arr[left], &arr[right]);Ni(arr, left 1, right - 1); } int main() {int ar…

将数字每千分位用逗号隔开

// 无小数版本 let format n > {let num n.toString() let len num.lengthif (len < 3) {return num} else {return format(num.slice(0,len-3)),num.slice(len-3)} } let resformat(1232327666663) // 1,232,323 console.log(res)// 有小数版本 let format n > …

C#有关里氏替换原则的经典问题答疑

目录 理解创建类型与变量类型很关键 先来理解变量类型。 再来理解创建类型。 有了正确的理解再来看疑问 里氏替换原则是面向对象七大原则中最重要的原则。 语法表现&#xff1a;父类容器装子类对象。 namespace 里氏替换原则 {class GameObject { } class Player : GameO…

[Vue3] vue-router路由守卫进阶

路由守卫&#xff0c;可以想象为古代御前侍卫&#xff0c;路由守卫&#xff0c;则是对路由进行权限控制 分类&#xff1a;全局守卫、独享守卫、组件内守卫 文章目录 1.全局前置-路由守卫2.全局后置守卫3.独享守卫4.组件内路由守卫5.keep-alive遇见vue-router6.路由器的两种工作…

详解Python安装requests库的实例代码

文章目录 前言基本用法基本的get请求带参数的GET请求解析json关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Python兼职渠道 前…

类加载器与反射

1&#xff1a;Java类加载机制 简介&#xff1a; 虚拟机把数据从Class文件加载到内存&#xff0c;并对数据进行校验&#xff0c;解析和初始化&#xff0c;最终形成可以被虚拟机直接使用的java类型。 2&#xff1a;JVM加载Class文件的原理机制 Java中的所有类&#xff0c;都需…

“茶叶创新:爆改营销策略,三个月狂销2300万“

我的朋友去年制作了一款白茶&#xff0c;并在短短三个月内将其销售量推到了2300万的高峰。你相信吗&#xff1f; 这位朋友并没有任何茶叶方面的经验&#xff0c;他只是一个有着冒险精神的企业家。他先找到了一家代工厂&#xff0c;帮助他把他的茶叶理念转化为现实。 当他把茶叶…