redisson分布式锁使用小记

如题所述

第1个回答  2024-09-05

首先关于redisson的介绍,这里就不搬运了,贴一下github原地址:

概述

由于我这里只是简单使用了redisson的分布式锁的功能,这里仅记录下锁的简单使用。

官方文档:8.分布式锁和同步器

此次所用锁为可重入锁

可重入锁(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还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

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

Redisson同时还为分布式锁提供了异步执行的相关方法:

RLocklock=redisson.getLock("anyLock");lock.lockAsync();lock.lockAsync(10,TimeUnit.SECONDS);Future<Boolean>res=lock.tryLockAsync(100,10,TimeUnit.SECONDS);

RLock对象完全符合Java的Lock规范。也就是说只有拥有锁的进程才能解锁,其他进程解锁则会抛出IllegalMonitorStateException错误。但是如果遇到需要其他进程也能解锁的情况,请使用分布式信号量Semaphore对象.

首先springboot整合redisson需要引入redisson的依赖:

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

亲测以上版本,配合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#获取失败最大重定向次数#nodes:#-192.168.104.101:6379lettuce:pool:max-active:1024#连接池最大连接数(默认为8,-1表示无限制如果pool已经分配了超过max_active个jedis实例,则此时pool为耗尽)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")publicclassRedisConfigProperties{privateIntegertimeout;privateIntegerdatabase;privateIntegerport;privateStringhost;privateStringpassword;privateclustercluster;publicstaticclasscluster{privateList<String>nodes;publicList<String>getNodes(){returnnodes;}publicvoidsetNodes(List<String>nodes){this.nodes=nodes;}}publicIntegergetTimeout(){returntimeout;}publicvoidsetTimeout(Integertimeout){this.timeout=timeout;}publicIntegergetDatabase(){returndatabase;}publicvoidsetDatabase(Integerdatabase){this.database=database;}publicIntegergetPort(){returnport;}publicvoidsetPort(Integerport){this.port=port;}publicStringgetHost(){returnhost;}publicvoidsetHost(Stringhost){this.host=host;}publicStringgetPassword(){returnpassword;}publicvoidsetPassword(Stringpassword){this.password=password;}publicRedisConfigProperties.clustergetCluster(){returncluster;}publicvoidsetCluster(RedisConfigProperties.clustercluster){this.cluster=cluster;}}

添加自动装配类:RedissonConfig.java

importgc.cnnvd.config.properties.RedisConfigProperties;importorg.redisson.Redisson;importorg.redisson.config.Config;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/***@authorlinmengmeng*@author2021-08-30*/@ConfigurationpublicclassRedissonConfig{@AutowiredprivateRedisConfigPropertiesredisConfigProperties;/***redis://host:port*/privatestaticfinalStringREDIS_ADDRESS="redis://%s:%s";///**//*集群模式-添加redisson的bean//*@return//*///@Bean//publicRedissonredisson(){////redisson版本是3.5,集群的ip前面要加上“redis://”,不然会报错,3.2版本可不加//List<String>clusterNodes=newArrayList<>();//for(inti=0;i<redisConfigProperties.getCluster().getNodes().size();i++){//clusterNodes.add("redis://"+redisConfigProperties.getCluster().getNodes().get(i));//}//Configconfig=newConfig();//ClusterServersConfigclusterServersConfig=config.useClusterServers()//.addNodeAddress(clusterNodes.toArray(newString[clusterNodes.size()]));//clusterServersConfig.setPassword(redisConfigProperties.getPassword());//设置密码//return(Redisson)Redisson.create(config);//}/***Redisson单机模式*@return*/@BeanpublicRedissonRedissonConfig(){Configconfig=newConfig();//config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(redisConfigProperties.getDatabase());config.useSingleServer().setAddress(String.format(REDIS_ADDRESS,redisConfigProperties.getHost(),redisConfigProperties.getPort())).setDatabase(redisConfigProperties.getDatabase()).setPassword(redisConfigProperties.getPassword());//没有密码可以不设置return(Redisson)Redisson.create(config);}}

创建测试接口测试分布式锁:

packagegc.cnnvd;importgc.cnnvd.framework.common.api.ApiResult;importlombok.extern.slf4j.Slf4j;importorg.redisson.Redisson;importorg.redisson.api.RLock;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";//分布式锁的key@AutowiredprivateRedisTemplateredisTemplate;@AutowiredprivateRedissonredisson;@PostMapping("/testLock11")publicApiResult<Boolean>testLock11(){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();//}Future<Boolean>res=lock.tryLockAsync(10,30,TimeUnit.SECONDS);log.info("-------tryLockAsync后isLocked-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{result=res.get();log.info("-------result:"+result);if(result){Thread.sleep(10000);if(lock.isHeldByCurrentThread()){log.info("-------isHeldByCurrentThread:{}",lock.isHeldByCurrentThread());}}}catch(InterruptedExceptione){e.printStackTrace();}catch(ExecutionExceptione){e.printStackTrace();}finally{log.info("-------666666-------unlock-isLocked:{}",lock.isLocked());if(lock.isLocked()){log.info("-------88888888-------解锁解锁:{}",lock.isLocked());lock.unlock();}}log.info("res.get:{}",result);returnApiResult.ok(lock.isLocked());}@PostMapping("/testLock12")publicApiResult<Boolean>testLock12(){StringlockKey=String.format(FORMAT_LOCKKEY,3);log.info("====================lockKey:{}",lockKey);RLocklock=redisson.getLock(lockKey);log.info("====================isLocked-1:{}",lock.isLocked());Future<Boolean>res=lock.tryLockAsync(5,2,TimeUnit.SECONDS);booleanlocked=lock.isLocked();log.info("====================isLocked-2:{}",locked);if(locked){if(lock.isHeldByCurrentThread()){log.info("====================锁住了,是我的锁");}else{log.info("====================锁住了,不是我的锁");}}BooleangetLock=null;log.info("====================getLock-2:{}",getLock);returnApiResult.ok(locked);}}

上面的代码看着很乱,当时摸索着打的日志,为了更好的理解加锁与解锁机制。

在testLock11接口里面添加线程睡眠,模仿程序运行占用锁,这时可以在redis里面看到我们加锁的key:

刚开始一直没有找到key,后来才发现,线程运行完成后,自动释放了锁,后面把睡眠时间拉长,才找到redis里面的key。

后面在用到代码里面,对加锁和锁的判断使用如下:

设置加锁的唯一标识

获取锁,并持有,做自己的业务逻辑

//尝试获取锁-异步,第一个时间表示:最多等待时间,第二个时间参数表示:上锁以后多久自动解锁Future<Boolean>res=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();时,如果锁已经释放,或者别的线程拿到锁了,当前线程释放锁会抛出异常:

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

2.踩坑异常1.Errorprocessingconditiononorg.springframework.boot.autoconfigure.cache.S

首次添加redisson依赖后,原来的redis配置不好使了,项目启动就报了上面的错。最后切换实例化CacheManager

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

redisson入门可参考:#SpringBoot整合Redisson(单机版)

集群版配置文件可参考:redisson版本_SpringBoot整合Redisson(集群版)

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

刚开始由于使用了旧版本的redisson,发现项目启动后不久,控制台抛出此异常

参考技术文献:

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

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

切换redisson版本,解决此异常。

其他参考博客:

SpringBoot整合Redisson

REDIS分布式锁REDISSON扩展这篇博客结合AOP整合了redisson锁,值得学习下。

锁的相关概念,理解:分布式锁,redisson是如何解决死锁问题

Redisson(1)分布式锁——如何解决死锁问题

使用Redisson实现分布式锁

如果翻到了最后,就再推荐2篇通俗易懂的博客:

SpringBoot整合Redis实现简单的分布式锁

SpringBoot整合Redisson实现分布式锁

logo设计

创造品牌价值

¥500元起

APP开发

量身定制,源码交付

¥2000元起

商标注册

一个好品牌从商标开始

¥1480元起

公司注册

注册公司全程代办

¥0元起

    官方电话官方服务
      官方网站八戒财税知识产权八戒服务商企业需求数字市场
相似回答
大家正在搜