当前位置:网站首页>redis实现分布式锁的原理
redis实现分布式锁的原理
2022-08-03 09:16:00 【m0_54853420】
redis实现分布式锁的原理
一、为什么使用分布式锁?
·>本地锁的局限性:
本地锁只能锁住当前服务,只能保证自己的服务,只有一个线程可以访问,但是在服务众多的分布式环境下,其实是有多个线程同时访问的同一个数据,这显然是不符合要求的。
·>分布式锁的概念:
分布式锁指的是,所有服务中的所有线程都去获得同一把锁,但只有一个线程可以成功的获得锁,其他没有获得锁的线程必须全部等待,等到获得锁的线程释放掉锁之后获得了锁才能进行操作。Redis官网中,set key value有个带有NX参数的命令,这是一个原子性加锁的命令,指的是此key没有被lock是,当前线程才能加锁,如果已经被占用,就不能加锁。
二、redis实现分布式锁的原理?
1.抢占分布式锁:
Java代码中的实现:
Boolean lock = redisTemplate.opsForValue().setIfAbsent( "lock","111");
·如果加锁成功(lock = true)**,就先执行相应的业务,
然后释放掉锁:redisTemplate .delete(key: "lock" );
·如果加锁失败(lock = false)**,就通过自旋的方式进行重试(比如递归调用当前方法)。
注意:
为了防止在执行删锁操作之前,程序因为出现异常导致在还没有执行到删锁命令之前,程序就直接抛出异常退出,导致锁没有释放造成最终死锁的问题。(可能会有人想到,把删锁操作放在finally里以保证删锁操作一定被执行到,但是万一在执行删锁操作的过程中,电脑死机了呢!结果锁还是没有被成功的释放掉,依然会出现死锁现象。)于是,初步想到的解决方式就是在加锁的时候,就给这个锁设置一个过期时间。这样的话,即使我们由于各种原因没有成功的释放锁,redis也会根据过期时间,自动的帮助我们释放掉锁。
2.加锁的同时设置过期时间:
在成功获取到锁之后,执行删锁操作之前,给锁lock设置一个过期时间,例如30秒。
redisTemplate.expire( "lock" , 30, TimeUnit.SECONDS);
这样一来,即使我们自己没有删除掉锁,到到了过期时间后,redis也会帮我们自动删除掉。
注意:
由于加锁和设置锁的过期时间这两步操作不是原子性的,所以可能会在这之间出现问题,导致还没来得及设置锁的过期时间,程序就中断了。所以,需要加锁和设置过期时间这两步必须是原子性不可分割的操作。
Redis中的原子性命令,set lock 111 EX 30 NX ,表示key为lock,值为111,有效时间是30秒,是个NX的原子性加锁操作,可以保证加锁和过期时间这两个操作要么同时成功,要么同时失败。
Java中的代码是:
Boolean lock = redisTemp1ate.opsForValue().setIfAbsent("lock" , "111",30,TimeUnit.SECONDS);
3.使用redis脚本解锁:
上面,我们通过在加锁的同时就给锁设置过期时间的方式,解决了自己因为各种原因无法成功删除掉锁的问题。但是,即使我们利用redis自动删除过期键的方式成功防止了出现死锁的问题,但是在删锁这里,依旧还会出现问题。
例如:
线程一在加锁的时候设置了30秒的过期时间,他成功的获得了锁,然后开始执行自己的业务逻辑部分,如果他的操作过于费事,需要执行40秒种,那么,当他的业务逻辑执行到30秒的时候,由于最开始设置了过期时间是30秒,此时,redis根据过期时间,将线程一占到的锁释放掉,其他线程立马前来占锁,如果线程二此时占锁成功,也开始执行自己的业务逻辑部分,刚执行了10秒,此时线程一执行完了所有的业务逻辑,准备删锁离开,那么线程一此时删锁操作删掉的其实是线程二的锁。但是此时此刻,线程二是需要用这个锁的,却意外的被线程一给释放掉了,这显然是不合理的。所以我们应该在删锁之前先判断一下,当前的锁是否是自己当时加的那把锁,以防止删错删成了别人的锁。
Java代码根据key得到value:
String lockValue = redisTemplate.opsForvalue( ).get("lock" );
将当前的值与最开始存进去的值进行比较,如果相同在执行删锁操作。
但这又会出现问题,试想,如果当前锁的有效时间是10秒,我们的逻辑代码执行了9秒,然后去远程redis中,取当前key对应的value。例如,从本地发送请求到redis查数据的过程花费了0.5秒,redis接收的命令并将对应的值查到共花费了0.3秒,到这里一共是9.8秒,然后,redis将查到的值传回来需要0.6秒,此时已经花费了10.4秒。此时此刻,我们从redis中得到的返回值确实是我们之前存进去的那个值,但实际上,在数据从redis传回来的半路上,我们的值就已经在redis中过期了,此时此刻,redis中存的数据,其实是其他线程存进去的,并不是我们当初存进去的那个数据了,我们此时执行的删锁操作,其实删除的还是其他人的锁。
到这里我们可以知道,删锁出现问题的原因同上面加锁时是一样的,加锁问题解决的方式是:让加锁和设置过期时间的操作是个原子不可分割的过程。同理,那删锁时,我们如果能够保证去redis获取值和删锁操作也是个原子不可分割的过程,就可以解决上述问题了。
为此,redis官网http://redis.cn/commands/set.html提供了一个用于解决此问题的解锁脚本。
Java代码通过解锁脚本进行删锁操作:
String script =
"if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
//使用函数执行删锁脚本
stringRedisTemplate.execute(new DefaultRedisScript<>(script,Integer.class),Arrays.asList("lock"),uuid);
三、Java代码实现redis分布式锁
综上,使用redis实现分布式锁的java代码如下:
先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦
边栏推荐
猜你喜欢
随机推荐
Index (3)
【LeetCode】zj面试-把字符串转换成整数
The display of the article list and the basics of creating articles and article details
STP普通生成树安全特性— bpduguard特性 + bpdufilter特性 + guard root 特性 III loopguard技术( 详解+配置)
LINGO 18.0 software installation package download and installation tutorial
LeetCode第三题(Longest Substring Without Repeating Characters)三部曲之二:编码实现
Flink Yarn Per Job - 创建启动Dispatcher RM JobManager
AcWing 3391. 今年的第几天?(简单题)
文章列表的显示 以及创建文章 还有文章详情的基本
When deleting a folder, the error "Error ox80070091: The directory is not empty" is reported. How to solve it?
MySQL_关于JSON数据的查询
机器学习(公式推导与代码实现)--sklearn机器学习库
chrome F12 network 保留之前请求信息
php中去重二维数组
SAP Analytics Cloud 和 SAP Cloud for Customer 两款 SaaS 软件的集成
删除文件夹时,报错“错误ox80070091:目录不是空的”,该如何解决?
PostgreSQL的架构
基于百度AI和QT的景物识别系统
Flink Yarn Per Job - 启动AM
多线程下的单例模式