Springboot使用redis的setnx和getset实现并发锁、分布式锁
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Springboot使用redis的setnx和getset实现并发锁、分布式锁相关的知识,希望对你有一定的参考价值。
参考技术A 在日常开发中,很多业务场景必须保证原子性。举几个例子:如果你只有一台服务器,只运行一个Java程序,那么可以使用Java语言自身的一些锁来实现原子性。但如果我们有多台服务器,甚至不同服务器上跑的是不同的语言。那这时候,我们就需要一个跨平台、跨语言的加锁方式。redis就是其中最方便的一种。
使用redis实现并发锁,主要是靠两个redis的命令:setnx和getset。
那我们的设计思路就是:
上面的代码使用了一个RedisService的类,里面主要是简单封装了一下redis的操作,你可以替换为自己的service。代码如下:
以上代码有任何疑问,可以点击右侧边栏联系作者。收费5毛~交个朋友,欢迎来撩!
版权声明:《Springboot使用redis的setnx和getset实现并发锁、分布式锁》为CoderBBB作者「ʘᴗʘ」的原创文章,转载请附上原文出处链接及本声明。
原文链接:https://www.coderbbb.com/articles/2
SpringBoot整合redis使用setnx完成分布式锁
spring boot 版本2.2.0
pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
yml配置redis地址端口
redis:
host: 127.0.0.1
port: 6379
使用springboot自动配置好的StringRedisTemplate来操作redis
分布式锁阶段一
直接使用setnx 加锁
/**
* redis分布式锁
* @return
*/
public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock()
//redis分布式锁 如果key不存在设置成功
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1");
if (lock)
//加锁成功 调用方法返回数据
Map<String, List<Catelog2Vo>> dataFromDB = getDataFromDB();
//释放锁
stringRedisTemplate.delete("lock");
return dataFromDB;
else
//没有分布式锁,等待,进入自旋
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
//自旋
return getCatalogJsonFromDbWithRedisLock();
存在问题:setnx抢占锁成功,业务代码或者程序在运行过程中宕机了,没有执行锁删除逻辑,就会造成死锁。
解决:设置锁的自动过期,即使没有手动删除,会自动删除;
分布式锁阶段二
使用expire 设置过期时间
/**
* redis分布式锁
* @return
*/
public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock()
//redis分布式锁 如果key不存在设置成功
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1");
if (lock)
//加锁成功 调用方法返回数据
// 设置过期时间 5分钟
stringRedisTemplate.expire("lock",5, TimeUnit.MINUTES);
Map<String, List<Catelog2Vo>> dataFromDB = getDataFromDB();
//释放锁
stringRedisTemplate.delete("lock");
return dataFromDB;
else
//没有分布式锁,等待,进入自旋
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
//自旋
return getCatalogJsonFromDbWithRedisLock();
这里还有一个问题 设置过期时间跟加锁不是一条语句 如果加锁成功但设置过期时间未成功 还是会出现问题
将加锁与设置过期时间原子完成
/**
* redis分布式锁
* @return
*/
public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock()
//redis分布式锁 如果key不存在设置成功 设置过期时间 5分钟
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1",1, TimeUnit.MINUTES);
if (lock)
//加锁成功 调用方法返回数据
Map<String, List<Catelog2Vo>> dataFromDB = getDataFromDB();
//释放锁
stringRedisTemplate.delete("lock");
return dataFromDB;
else
//没有分布式锁,等待,进入自旋
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
//自旋
return getCatalogJsonFromDbWithRedisLock();
问题 :删除锁直接删除, 由于业务处理时间长,锁过期了,我们直接删除,有可能把别人正在持有的锁删除了,
解决:占锁的时候,值指定为uuid,每个人匹配是自己的锁才删除
分布式锁阶段三
匹配uuid
/**
* redis分布式锁
* @return
*/
public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock()
String uuid = UUID.randomUUID().toString();
//redis分布式锁 如果key不存在设置成功 设置过期时间 5分钟
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,1, TimeUnit.MINUTES);
if (lock)
//加锁成功 调用方法返回数据
// 设置过期时间 5分钟
// stringRedisTemplate.expire("lock",5, TimeUnit.MINUTES);
Map<String, List<Catelog2Vo>> dataFromDB = getDataFromDB();
//获取到锁的值
String curLock = stringRedisTemplate.opsForValue().get("lock");
if (curLock.equals(uuid))
//如果curLock的值等于当前uuid的值 那么就删除锁
stringRedisTemplate.delete("lock");
return dataFromDB;
else
//没有分布式锁,等待,进入自旋
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
//自旋
return getCatalogJsonFromDbWithRedisLock();
问题
如果 String curLock = stringRedisTemplate.opsForValue().get(“lock”);语句在查询的时候key没有过期,但执行删除之前key过期了 我们删除的还是别人的锁。
解决 使用lua脚本 原子的查询与删除锁
/**
* redis分布式锁
* @return
*/
public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock()
String uuid = UUID.randomUUID().toString();
//redis分布式锁 如果key不存在设置成功 设置过期时间 5分钟
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,1, TimeUnit.MINUTES);
if (lock)
//加锁成功 调用方法返回数据
Map<String, List<Catelog2Vo>> dataFromDB = getDataFromDB();
//获取到锁的值
String curLock = stringRedisTemplate.opsForValue().get("lock");
if (curLock.equals(uuid))
//如果curLock的值等于当前uuid的值 那么就删除锁
stringRedisTemplate.delete("lock");
String script="if redis call('get',KEYS[1]) == ARGV[1] then return redis call('del',KEYS[1]) else return 0 end";
//new DefaultRedisScript<Integer>(script) 泛型为返回值的类型 参数为脚本
Integer execute = stringRedisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.TYPE), Arrays.asList("lock"), uuid);
return dataFromDB;
else
//没有分布式锁,等待,进入自旋
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
//自旋
return getCatalogJsonFromDbWithRedisLock();
以上是关于Springboot使用redis的setnx和getset实现并发锁、分布式锁的主要内容,如果未能解决你的问题,请参考以下文章