栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Redis分布式锁原理(一)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Redis分布式锁原理(一)

这样看来,redis的确是可以用来实现分布式锁,原理就是使用set nx命令进行设值,若返回成功则代表拿锁成功,返回失败则代表拿锁失败。

三、实现redis分布式锁需要注意哪些问题?

首先来看这样一段代码。

@Override

public List> findList() {

//尝试获取分布式锁,步骤一

Boolean lock = redisTemplate.opsForValue().setIfAbsent(“lock”, “齐天小圣”);

if (lock) {

//加锁成功,执行业务,步骤二

List> resultList = findListByDB();

//删除锁,步骤三

redisTemplate.delete(“lock”);

return resultList;

} else {

//加锁失败,自旋重试,即重新调用本方法。

try {

Thread.sleep(300);

} catch (Exception e) {

e.printStackTrace();

}

return findList();

}

}

乍一看用redis完成一个分布式锁的整体逻辑好像也不是很复杂,无非就是先去获取分布式锁,如果拿到锁了就去执行相应的业务,没有拿到锁就自旋重试。但其实上面这段代码漏洞百出,实际想要完成一个完美的分布式锁是很复杂的,我们需要注意以下问题:

**1、死锁问题:**如果某服务器拿锁成功,执行到步骤二时突然宕机没能正常的执行步骤三释放锁,那么其他正在等待锁的服务器则永远也拿不到锁了,这就是死锁问题。

解决这个问题很简单,只需要在拿锁之后给锁上一个超时时间即可,即使业务过程中出现问题导致不能释放锁,到了过期时间redis也会自动帮我们去把这个锁删除。需要注意的是,千万不能把“获取锁”和“设置超时时间”在代码中分成两步执行,如下:

原因在于这两个步骤分开执行没有保证原子性,拿锁到设置过期时间之间是存在时间差的,如果在这之间机器宕机了还是会存在上述问题,解决办法就是在占锁的同时设置过期时间。

**2、业务时间 > 超时时间:**假设这样一个场景,业务1拿锁成功并设置过期时间30s,但业务1比较复杂需要花费40s,那么到了30s后业务1的锁就已经失效了,此时业务2抢到了锁也进来执行相应的逻辑,那么此时业务1和业务2都在执行各自的业务逻辑,可能会操作相同的数据资源造成违反资源互斥的现象。问题还没完,又过去了10s,业务1执行完了,理所当然的就去删锁,那么自然就会把业务2手里的锁删掉,业务2一脸懵逼。。。这就造成了锁误删的现象。

先解决锁误删的问题,其实很简单,只需要保证谁拿的锁谁就有资格删就可以了,我们在获取锁的时候设置了一个value,此时就派上了用场,把每个业务的value都设置uuid,最后删锁的时候先去redis获取锁对应的值,如果这个值等于uuid才有资格执行删锁命令。需要注意的是这里也需要保证原子性,因为去远程redis获取锁对应的值再返回来也是有时间差的,如果业务1去远程获取到锁的value为“1111”,回来的过程中锁过期了,此时业务2拿到了锁开心的去执行它的业务去了,好景不长,业务1回来判断出锁的value是等于自己的uuid,于是又理所当然的把锁删掉了,业务2又一脸懵逼。。。。这里就需要使用lua脚本解锁,lua脚本就是为了保证这两段操作的原子性,解锁脚本内容如下:

if redis.call(“get”,KEYS[1]) == ARGV[1]

then

return redis.call(“del”,KEYS[1])

else

return 0

end

最后优化后的代码如下:

@Override

public List> findList() {

//尝试获取分布式锁,并设置过期时间

String uuid = UUID.randomUUID().toString 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 ();

Boolean lock = redisTemplate.opsForValue().setIfAbsent(“lock”, uuid, 30, TimeUnit.SECONDS);

if (lock) {

//加锁成功,执行业务

List> resultList;

try{

resultList = findListByDB();

} finally {

//获取值进行对比,若对比成功则有资格进行删锁,需使用lua脚本保证原子性

String script = “if redis.call(“get”,KEYS[1]) == ARGV[1]n” +

“thenn” +

" return redis.call(“del”,KEYS[1])n" +

“elsen” +

" return 0n" +

“end”;

Long result = redisTemplate.execute(new DefaultRedisScript(script, Long.class)

, Arrays.asList(“lock”), uuid);

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/837638.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号