芯片 缓存 晶体管 寄存器 内存 运算器
主页 正文

Redisson解决Redis分布式锁问题优化解析

Redisson解决Redis分布式锁提前释放问题

简介:

在分布式场景中,相信大家或多或少都需要使用分布式锁来访问重要资源或者控制耗时操作的并发度。

当然,实现分布式锁的方案有很多,比如数据库、Redis、ZooKeeper等。
本文主要收集了一个网上的案例来讲解redis分布式锁的相关实现。

1.问题描述:

某天线处理数据冗余时出现问题,经排查发现单次处理时间较长,分布式redis锁提前释放,导致。
并发处理。
同样的要求。

实际上,这是一个锁更新问题。
对于分布式锁,我们需要考虑设置的锁需要多长时间才会过期,以及如果发生异常如何释放锁?

以上问题就是本文要讨论的主题。

2.原因分析:

项目使用? 一个比较简单的自定义分布式锁,设置默认过期时间10秒,避免死锁,如下:

overridefunlock(){while (true){//尝试获取锁 if(tryLock()){返回}尝试{Th read.sleep(10)}catch(e:InterruptedException){e.printStackTrace()}}}overridefuntryLock():Boolean{valvalue=getUniqueSign()//随机字符串 v aflag=redisTemplate!!.opsForValue().setIfAbsent(name,value,10000,TimeUnit.MILLISECONDS)if(flag!=null&&flag){VALUE_lOCAL.set(v) alue)INTO_NUM_LOCAL.set(if(INTO_NUM_LOCAL.get()!=null)INTO_NUM_LOCAL.get()+1else1)returntrue}returnfalse

缺乏自动锁更新实现。

3. 解决方案: 1.思考:?

在这种场景下,你可以思考如何自动更新锁——当工作没有完成时,当然你也可以自定义实现,比如解锁后台线程定期更新锁这些线程的锁。

Redisson也是依靠这个思想来实现自动更新的分布式锁,各种异常情况也被充分考虑,综合考虑了Redisson分布式锁解决方案的改进。

2. 简单的Redisson配置: @Configuration@EnableConfigurationProperties(RedissonProperties::class)classRedissonConfig{@BeanfunredissonClient(redissonProperties:RedissonProperties):RedissonClient{valconfig=Config()valsingleServerConfig=redissonProperties.singleServerConfig! setConnectionMin.setAddress(singleServerConfig.address).setDatabase(singleServerConfig.database).setUsername(singleServerConfig.username).setPassword(singleServerConfig.password)。
setRetryAttempts(singleServerConfig)。
.retryAttempts).setTimeout(singleServerConfig.timeout)returnRedisson.create(config)}}@ConfigurationProperties(pre fix="xxx.redisson")classRedissonProperties{varsingleServerConfig:SingleServerConfig?=null}

Redis服务采用腾讯云哨兵模式架构。
该结构体开放了一个代理地址供外部访问,因此您可以在此处配置独立模式配置。

如果自己创建redis Sentinel模式结构体,则需要根据文档配置相关必要参数

3、使用示例: @AutowiredlateinitvarredissonClient:RedissonClient funxxx () {.. .vallock=redissonClient.getLock("mylock")lock.lock()try{ }finally{lock.unlock()}

使用和错误 JDK提供的锁很相似? 是不是很简单呢?

正是Redisson这样优秀的开源产品的出现,让我们能够投入更多的时间在业务开发上……

4、源码分析

我们来看看Redisson的传统分布式锁的实现主要分解RedissonLock

1 erridepublicvoidlock(){try{lock(-1,null,false);}catch(InterruptedExceptione){thrownewIllegalStateException();}}//租期即过期时间,-1表示如果不设置则系统默认为30sprivatevoidlock(longleaseTime,TimeUnitunit,booleaninterruptible) throwsInterruptedException{//尝试获取 锁,如果能拿到就直接回去 longthreadId=Thread.currentThread().getId();Longttl=tryAcquire(-1,leaseTime,unit,threadId);//lockacquiredif(ttl==null){return;}RFuturefuture=subscribe(threadId);if(中断){commandExecutor.syncSubscriptionInterrupted(future);}else{commandExecutor.syncSubscription(future);}// 如果无法获取锁,只需尝试旋转,直到成功获取锁或出现异常,该尝试通常会终止{while(true){ttl=tryAcquire(-1,leaseTime,unit,threadId);//lockacquiredif(ttl) = =null){break;} }}最后{取消订阅(future,threadId); }} 1.1,tryAcquireprivateLongtryAcquire(longwaitTime,longleaseTime,时间 Unitunit,longthreadId){returnget(tryAcquireAsync(waitTime,leaseTime,unit,threadId));}privateRFuturetryAcquireAsync(longwaitTime,longleaseTime,TimeUnitunit,longthreadId){RFuturettlRemainingFuture;//调用true操作获取锁是 if(leaseTime!=-1){ttlRemainingFuture=tryLockInnerAsync(waitTime,leaseTime,unit,threadId,RedisCommands.EVAL_LONG);}else{ttlRemainingFuture=tryLockInnerAsync(waitTime,internalLockLeaseTime,TimeUnit.MILLISECONDS, threadId,RedisCommands.EVAL_LONG);}ttlRemainingFuture.onComplete((ttlRemaining,e)->{if(e!=null){return;}//l ockacquired//这里获取锁成功,尝试更新锁 if(ttlRemaining==null){if(leaseTime!=-1){internalLockLeaseTime=unit.toMillis(le aseTime);}else{scheduleExpirationRenewal(threadId);}}});returnttlRemainingFuture;}//已经执行锁操作通过脚本 luaRFuturetryLockInnerAsync(longwaitTime,longleaseTime,TimeUnitunit,longthreadId,RedisStrictCommandCommand){ //如果key不存在也可以,直接设置,设置过期时间 //如果key存在,有两种情况需要考虑 // - 同一个线程获取重入锁,并且直接对应field(是一个值 getLockName(threadId)) +1//- 竞争锁上的不同螺纹。
这次锁失败,直接返回这个key对应的过期时间 returnevalWriteAsync(getRawName(),LongCodec.INSTANCE,command,"if(redis.call('exists',KEYS[1])==0)then" +"redis.call('hincrby',KEYS[1],ARGV[2],1);"+"redis.call('pexpire',KEYS[1],ARGV[1]); "+"returnnil;"+"end;"+"if(redis.call('hexists',KEYS[1],ARGV[2])==1)then"+"redis.call('hincrby',KEYS [1],ARGV[ 2],1);"+"redis.call('pexpire',KEYS[1],ARGV[1]);"+"returnnil;"+"end;"+"returnredis.call('pttl',KEYS [1]);, 公司 llections.singletonList(getRawName()),unit.toMillis(leaseTime),getLockName(threadId)); 1.2.续订

由 sch eduleExpirationRenewal 续订 锁

protectedvoidscheduleExpirationRenewal(longthreadId){ExpirationEntryentry=newExpirationEntry();ExpirationEntryoldEntry=EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(),entry);if(oldEnt) ( ee==null){return;}//设置设置重要任务 Late,持续时间InternalLockLeaseTime/3后执行,定期更新锁 Timeouttask=commandExecutor.getConnectionManager().newTimeout(newTimerTask(){@Overridepublicvoidrun(Timeouttimeout)throwsException{ExpirationEntryent=EXPIRATION_RENEWAL_ MAP.get(getEntryName());if(ent==null){return;}LongthreadId=ent.getFirstThreadId();if(threadId==null){return ;}//更新命令操作已经执行 RFuture<布尔> future=renewExpirationAsync(threadId);future.onComplete((res,e)->{if(e!=null){ log.error("Can'tupdatelock"+getRawName()+"expiration",e);EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}//本次续订后,继续给自己安排,以达到持续续订的效果if( res ){//rescheduleitselfrenewExpiration();}});}},internalLockLeaseTime/ 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);}//所谓续订,就是延长过期时间 protectedRFuturerenewExpirationAsync(longthreadId){//如果当前key和线程存在,则延长过期时间,成功返回1,失败返回0 returnevalWriteAsync(getRawName(),LongCo dec.INSTANCE,RedisCommands.EVAL_BOOLEAN,"if(redis.call('hexists',KEYS[1],ARGV[2])==1)then"+"redis.call('pexp ire',KEYS[1],ARGV[1]);"+"return1;"+"end;"+"return0;",Collections.singletonList(getRawName()),internalLockLeaseTime,getLockName(threadId));

2. 解锁过程 publicvoidunlock(){try{get(unlockAsync(Thread.currentThread().getId()));}catch(RedisExceptione){. .}}publicRFutureunlockAsyn c(longthreadId){RPromiseresult=newRedissonPromise<>();//执行解锁操作 RFuturefuture=unlockInnerAsync(threadId);//操作后做什么成功 Future.onComplete((opStatus ,e) ->{//取消续订任务cancelEx pirationRenewal(threadId); });returnresult;}protectedRFutureunlockInnerAsync(longthreadId){//如果当前线程对应的key和记录不再存在,直接返回null //无字段(即getLockName ( threadId)) 对应的值减1 //- 如果减1后该值仍然大于0,则重新延长过期时间 //- 如果 若减后值小于等于0,则立即删除该key并发布订阅消息returnevalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if(redis.call('hexists',KEYS[1],ARGV[3])==0 )then"+"returnnil;"+"end;"+"localcounter=redis.call('hincrby',KEYS[1],ARGV[3],-1);"+"if(counter>0)then" +"redis.call('pexpire',KEYS[1],ARGV[2]);"+"return0;"+"else"+"redis.call('del' ,KEYS[1]);"+"redis.call('发布',KEYS[2],ARGV[1]);"+"return1;"+"end;"+"returnnil;",Arrays.asList( getRawName(),getChannelName()),LockPubSub.UNLOCK_MESSAGE,internalLockLeaseTime,获取 LockName(threadId));

以上就是redisson客户端工具添加/解锁分布式redis锁的具体实现,主要解决以下问题

? ?1. 死锁问题:设置过期时间

2、重入问题:重入+1,释放锁-1,当value = 0时,表示完全释放锁

? ?3. 更新问题:可以解决锁提前释放的问题

4版本:谁持有锁谁就释放

总结:

本文是通过网上提问提交的,并且通过流行的实现方案针对redis分布式锁,最终选择了redisson方案; Redisson具体实现细节分析

相关参考:

Redisson官方文档-分布式锁和同步器

Redisson官方文档-配置方法

CSDN-如何使用Redis实现分布式锁?

原文:https://juejin.cn/post/7101679218408292382

最强分布式锁工具:Redisson

Redisson概述

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

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

分布式锁

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

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

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

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

RLock

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

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

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

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

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

公平锁

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

总结

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

热门资讯
三种总线的使用模式
深度解析网站性能提升利器——Redis缓存应用与优化
基于51单片机的简易计算器
PWM控制原理详解及优势介绍
51单片机pwm输出正弦波
半导体与集成电路和芯片的关系
社区书记履职情况总结
与门真值表口诀