Redisson分布式锁详解:实现与使用技巧

创始人
2024-12-14 18:57:49
0 次浏览
0 评论

最强分布式锁工具:Redisson

Redisson概述

Redisson是一个基于Redis的Java数据网格。
它提供了许多通用的分布式Java对象和分布式服务,旨在简化分布式操作,提高开发效率。
Redisson不仅提供基本的分布式对象,还提供高级的分布式服务,可以解决许多常见的分布式问题。

Redisson、Jedis和Lettuce之间的区别在于,它提供了更高级别的抽象,并且更易于使用。
Jedis和Lettuce主要提供Redis命令的封装,而Redisson则基于Redis、Lua和Netty打造成熟的分布式解决方案。
官方甚至推荐使用Redisson作为Redis工具包。

分布式锁

分布式锁是并发业务中的关键组件,用于保证多个进程或线程对共享资源的互斥访问。
分布式锁实现通常包括顺序节点、表级锁、悲观锁、乐观锁和RedissetNx命令。

Redisson提供了一种使用Lua脚本实现原子操作的简单方法,保证锁的获取和释放过程是原子的,从而避免并发问题。
Lua脚本使用Rediseval命令执行,该命令提供一次性原子操作并强制锁定互斥性。

为了确保重入,Redisson在Lua脚本中引入了一个计数器,用于跟踪同一线程获取同一锁的次数。
解锁后,计数器递减直至达到零,并执行删除操作以确保锁被正确释放。

此外,Redisson还支持分布式锁扩展功能,允许您在锁时间到期前自动延长锁时长,保证业务运行不间断。
这个特性在高并发场景下尤其重要可以有效防止锁丢失或者超时导致的并发冲突。

RLock

RLock是Redisson实现的核心分布式锁接口。
它继承自Java并行包中的Lock接口和RedissonRLockAsync用户接口。
RLock提供了锁定和解锁方法来实现Netty上异步操作的基本通信。

加锁过程通常涉及以下步骤:

根据导入配置,Redisson实例并使用RLock接口调用tryLock方法尝试获取锁。

当尝试获取锁。
阻塞时,Redisson会在Redis操作中使用Lua脚本来实现阻塞和计数逻辑。
如果锁超时时间不为-1,则会运行定时任务(watchDog)自动续订锁,以保证业务执行完成之前锁不会过期。

解锁过程包括从Redis中删除与锁对应的哈希表或键,并更新计数器以确保锁被正确释放。

公平锁

Redisson也提供了公平锁的实现。
它使用RedisList和ZSet数据结构来实现公平队列,并按照线程请求的顺序分配锁,保证公平性。
线程获取锁。
公平锁通过Lua脚本维护Redis中的等待队列和超时设置,并实现获取锁、释放锁和计数锁的逻辑。

总结

Redisson作为一个强大的分布式工具,通过提供丰富的分布式对象和高级服务,简化了分布式编程。
提供分布式锁、多机锁、红锁等场景的解决方案,适合需要分布式锁功能的项目。
借助Redisson,开发人员可以更加专注于业务逻辑的实现,而将分布式问题的解决交给Redisson。

redisson分布式锁使用小记

首先介绍一下redisson。
我不会在这里传递它。
我把github根地址贴出来:

概述

因为我这里只是使用redisson的分布式锁功能。
我刚刚在这里记录了密钥。
使用简单。

官方文档:八、分布式锁和同步器

这次用到的关键是可重入锁

ReentrantLock

基于Redis的Redisson再分布式RLockJava对象实现了java.util.concurrent.locks.Lock接口。
它还提供异步(Async)、反射(Reactive)和RxJava2标准接口。

RLocklock=redisson.getLock("anyLock");//最常用的方法lock.lock();

大家都知道,如果负责存储这个分配密钥的Redisson节点损坏了,当锁的时候处于锁定状态时,会出现锁定状态。
为了避免这种情况,Redisson内部提供了一个看门狗来监控锁。
它的作用是在关闭Redisson实例之前不断延长锁的有效期。
默认情况下,看门狗锁定超时时间为30秒,也可以通过修改Config.lockWatchdogTimeout单独指定。

此外,Redisson还通过lock方法提供了RentTime参数来指定锁定时间。
该时间到期后,锁将自动解锁。

//加锁后10秒自动解锁//无需调用unlock方法手动解锁lock.lock(10,TimeUnit.SECONDS);//尝试锁定,最多等待100秒,自动10秒;afterlockUnlockbooleanres=lock.tryLock(100,10,TimeUnit.SECONDS);if(res){try{...}finally{lock.unlock();}

Redisson也分为Redisson锁提供异步执行的相关方法设置:

RLocklock=redisson.getLock("anyLock");lock.lockAsync();lock.lockAsync(10,TimeUnit.SECONDS);Futureres=lock.tryLockAsync(100,10,TimeUnit.SECONDS);

RLock对象完全符合JavaLock规范。
这意味着,只有拥有密钥的进程才能解锁它。
如果其他进程解锁,则会抛出IllegalMonitorStateException错误。
但如果遇到其他进程需要能够解锁的情况,请使用分布式Semaphore对象。

首先,springboot集成了redisson,需要引入subs属于redisson:

<依赖项>org.redissonredisson-spring-boot-starter3.13.6

我亲自测试了上述版本,配合springboot版本:2.2.5.RELEASE正常使用。

独立redis实例的配置文件与原来的springboot集成redis文件相同。

spring:#Redis配置redis:timeout:6000#连接超时时长(毫秒)password:huauN@2021database:0host:192.168.104.64port:6379#cluster:#max-redirects:3#最大重定向失败次数#节点:#-192.168.104.101:6379lettuce:pool:max-active:1024#连接池最大连接数(默认为8,-1表示无限制。
如果池分配了超过max_active的jedis实例,则池已耗尽max-wait:10000#连接的最大等待时间,单位毫秒,默认为-1,意思是是它永远不会超时。
超时后会抛出JedisConnectionExceptionmax。
-idle:10min-idle:5

Redis配置映射类RedisConfigProperties.java

importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.stereotype.Component;importjava.util.List;/***配置映射类Redis**@authorlinmengmeng*@date2021-03-11**/@Component@ConfigurationProperties(prefix="spring.redis")publicclassRedisConfigPr操作{privateIntegertimeout;privateIntegerdatabase;privateIntegerport;privateStringhost;privateStringpassword;privateclustercluster;publicstaticclasscluster{privateListnodes;publicListgetNodes(){returnnodes;}publicvoidetNodes(Listnodes){this.nodes=nodes;}}publicIntegergetTimeout(){returntimeout;}publicvoidetTimeout(Integertimeout){this.timeout=timeout;}publicIntegergetDatabase(){returndatabase;}publicvoidetDatabase(Integerdatabase){this.database=database;}publicIntegergetPort(){returnport;}publicvoidetPort(Integerport){this.port=port;}publicStringgetHost(){returnhost;}publicvoidetHost(Stringhost){this.host=host;}publicStringgetPassword(){returnpassword;}publicvoidsetPassword(Stringpassword){this.password=password;}publicRedisConfigProperties.clustergetCluster(){returncluster;}publicvoidetCluster(RedisConfigProperties.clustercluster){this.cluster=cluster;}

添加自动装配类:RedissonConfig.java

importgc.cnnvd.config.properties.RedisConfigProperties;importorg.redisson.Redisson;importorg.redisson.config.Config;importorg.springframework.beans.factory.annotation;.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/***@authorlinmengmeng*@author2021-08-30*/@ConfigurationpublicclassRedissonConfig{@AutowiredprivateRedisConfigPropertiesredisConfigProper重要system;/***redis://host:port*/privatestaticfinalStringREDIS_ADDRESS="redis://%s:%s";///**//*集群模式-添加redissonBean//*@return//*////@Bean//publicRedissonredisson(){////redisson版本为3.5。
集群IP前必须添加“redis://”,否则会报错。
3.2无需添加//ListclusterNodes=newArrayList<>();//for(inti=0;i创建接口检查分发密钥的测试接口:

packagegc.cnnvd;importgc.cnnvd.framework.common.api.ApiResult;importlombok.extern.slf4j.Slf4j;importorg.redisson.Redisson;importorg.redisson.api;importorg。

springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.util.concurrent.ExecutionException;importjava.util.concurrent.Future;importjava.util.concurrent.TimeUnit;/***@Autherlinmengmeng*@Date2021-09-0115:37*/@Slf4j@RestController@RequestMapping("/tourist")publicclassTestRedissonLockController{privatestaticStringFORMAT_LOCKKEY="testLockKey:%s";//分布式锁Lock@AutowiredprivateRedisTemplateredisTemplate;@AutowiredprivateRedissonredisson;@PostMapping("/testLock11")publicApiResulttestLock11(){StringlockKey=String.format(FORMAT_LOCKKEY,3);log.info("-------lockKey:{}",lockKey);RLocklock=redisson.getLock(lockKey);log.info("--------isLocked-1:{}",lock.isLocked());//try{//Thread.sleep(10000);//}catch(InterruptedExceptione){//e.printStackTrace();//Futureres=lock.tryLockAsync(10,30,TimeUnit.SECONDS);log.info("--------tryLockAsyncisLocked-2:{}",lock.isLocked());try{Thread.sleep(10000);}catch(InterruptedExceptione){e.printStackTrace();}if(lock.isLocked()){log.info("-----10秒后----isLocked-3:{}",lock.isLocked());//thrownewBusinessException("检查获取后发生异常锁");}if(lock.isHeldByCurrentThread()){log.info("--------isHeldByCurrentThread:{}",lock.isHeldByCurrentThread());}booleanresult=false;try{结果=res.get();log.info("--------result:"+结果);if(结果){Thread.sleep(10000);if(lock.isHeldByCurrentThread()){log.info("--------isHeldByCurrentThread:{}",lock.isHeldByCurrentThread());}}}catch(InterruptedExceptione){e.printStackTrace();}catch(ExecutionExceptione){e.printStackTrace();}结束相同{log.info("-----666666-----unlock-isLocked:{}",lock.isLocked());if(lock.isLocked()){log.info("--------88888888-------UnlockUnlock:{}",lock.isLocked());lock.unlock();}}log.info("res.get:{}",结果);returnApiResult.ok(lock.isLocked());}@PostMapping("/testLock12")publicApiResulttestLock12(){StringlockKey=String.format(FORMAT_LOCKKEY,3);log.info("=====================lockKey:{}",lockKey);RLocklock=redisson.getLock(lockKey);log.info("========isLocked-1:{}",lock.isLocked());相似的Hybridres=lock.tryLockAsync(5,2,TimeUnit.SECONDS);booleanlocked=lock.isLocked();log.info("=====================isLocked-2:{}",已锁​​定);if(已锁定){if(lock.isHeldByCurrentThread()){log.info("=====================它被锁了,它是我的");}else{log."========它被锁了,这不是我的锁");}}BooleangetLock=null;log.info("=======getLock-2:{}",getLock);returnApiResult.ok(locked);}上面的代码看起来很乱。
当时我正在挖掘记录,以更好地了解锁定和解锁机制。

在testLock11接口中添加线程睡眠模式,以模拟运行程序和锁捕获。
此时就可以在redis中看到我们锁定的key了:

一开始没有找到key,后来发现之后线程运行完毕,锁自动释放,然后延长睡眠时间,在redis中查找key。

在后面使用的代码中,标识key和key如下:

设置key的唯一标识

获取key并保留它,实现自己的业务逻辑

//尝试获取锁——异步,第一个表示:maxtimeout,第二个时间参数表示:will加锁后多久自动解锁Futureres=lock.tryLockAsync(5,5,TimeUnit.SECONDS);if(lock.isLocked()&&lock.isHeldByCurrentThread()){//处理业务逻辑//解锁if(lock.isLocked()){lock.unlock();}if(!lock.isLocked()){log.info("成功解锁");}return;

我使用:lock.isLocked()&&lock.isHeldByCurrentThread()来确定锁。
这确保只有一个线程进入锁部分。

什么时候释放锁时,添加了另一个裁决:lock.isLocked(),以避免业务逻辑花费的时间超过锁的自动释放时间。
当执行lock.unlock();时,如果lock.已经释放,或者其他线程收到锁,当前线程释放锁时抛出异常:

手动释放或者过期自动释放

2.陷阱异常1.Errorprocessingconditiononorg.springframework.boot.autoconfigure.cache.S

第一次添加redisson依赖后,初始redis配置不起作用,项目启动head时报上述错误。
最后继续初始化CacheManager

参考SpringBoot通过Cacheable注解完成redis缓存功能

开始使用redisson请参考:#SpringBoot集成Redisson集成(单机版)

集群版配置文件请参考:redisson版本_SpringBootRedisson集成(集群版)

2.o.redisson.client.handler.CommandsQueue:Exceptionoccurred.Channel:

起初,由于使用旧版本的redisson,我注意到项目启动后控制台就抛出了这个异常

技术参考文档:

https://www.cnblogs.com/junge8618/p/9241927.html

https://www.jianshu.com/p/a89dbefb8f74

切换redisson版本解决该异常。

其他参考博客:

SpringBoot集成Redisson

REDIS分发密钥REDISSON扩展这篇博客集成了redisson密钥结合AOP,非常值得学习。

加锁的相关概念、理解:分布式加锁、Redisson如何解决死锁问题

Redisson(一)分布式加锁如何解决死锁问题规则

使用Redisson实现分布式锁

如果读到最后,我再推荐两篇更容易理解的博客:

SpringBoot集成Redis部署单一分布式密钥简单

SpringBoot集成Redisson部署分布式锁

热门文章
1
Python排列组合与循环运用技巧解析 怎样使用Python进行排列组合?对于这类问题,我们可以使用分割循环来执行转换和...

2
C语言实现字符串倒序输出教程 请教C语言字符串倒序输出#include#includevoidmain(){c...

3
Python字符串比较原理:基于ASCI... Python基础:如何比较两个字符串对象在Python中,字符串大小比较是基于字...

4
Java工具类:使用Apache POI... java实现读取word文件、读取表格1、导入对应的依赖包org.apache....

5
C语言字符串转整数:两种实现方法详解 怎么把字符串转换成整数?将字符串转换为整数有两种方法:1.使用C语言自带的库函数...

6
Python列表格式化输出技巧:f-st... python中请问怎么格式化输出列表在Python中,您可以使用字符串格式化函数...

7
探索非传统方法:JAVA实现100-99... JAVA,输出100到999的素数,,代码如下,但这不是传统的查找素数的方法。公...

8
C语言实现字符串大小写转换:字符类型判断... C语言把一个字符串里所有的大写字母换成小写字母,小写字母换成大写字母.其他字符保...

9
Python字符串格式化:深入理解for... Python小知识:用format格式化输出字符串Python使用format格...

10
程序员浪漫表白:用代码谱写烟花般爱情的编... 程序员的表白代码程序员信条第一语言:Java代码翻译:我每天爱你一点,直到我死代...