springBoot实现redis分布式锁
参考:https://blog.csdn.net/weixin_44634197/article/details/108308395
、、
使用redis的set命令带NX(not exist)参数实现分布式锁
NX:只有当不存在时,才可以set;成功set会返回OK,不成功返回null

//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() { //1、占分布式锁。去redis占坑
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除锁
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
阶段二 独立加上分布式锁的过期时间

//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() { //1、占分布式锁。去redis占坑
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
if(aBoolean){
//加锁成功 执行业务
//2、设置过期时间
stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS);
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除锁
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
阶段三 原子占锁和设置过期时间

//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() { //1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock",30,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除锁
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
阶段四 删锁进行权限uuid匹配

//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() { //1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
String lock = stringRedisTemplate.opsForValue().get("lock");
if(uuid.equals(lock)){
//删除自己的锁
stringRedisTemplate.delete("lock");
}
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
阶段五 lua脚本 删锁原子操作

//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() { //1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//获取值 + 对比 + 删除 必须是原子操作 lua脚本解锁
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then " +
" return redis.call('del', KEYS[1])" +
"else " +
" return 0 " +
"end";
Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("lock"), uuid); return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
阶段六 最终结果
不论业务是否正确完成都删除自己建立的锁
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() { //1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = null;
try {
dataFromDB = this.getDataFromDB();
}finally {
//获取值 + 对比 + 删除 必须是原子操作 lua脚本解锁
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then " +
" return redis.call('del', KEYS[1])" +
"else " +
" return 0 " +
"end";
Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("lock"), uuid);
}
return dataFromDB;
}else {
//加锁失败 重试 自旋
//睡眠
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonFromDBWithRedisLock();
}
}
- 本博文通过进阶的形式 不断提出问题以及解决思路,一步一步完善代码,实现具有高可靠性的分布式锁功能.
自己实现redis分布式锁
通过redis实现分布式锁
public class RedisLockImpl {
private static final long EXPIRETIME = 3000l;
public Map<String,Object> getRedisLock() {
//分布式锁实现
if (redisLock.lock("redisKey", EXPIRETIME, 0, 0)) {
try {
//续命
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(EXPIRETIME/2);//这里休眠设置的超时时间的一般
}catch (InterruptedException e){
e.printStackTrace();
}
//判断key是否存在 如果存在就重新设置超时时间
if (redisLock.hasExists(couponLock)){
//续命
boolean b = redisLock.setExpireTime(couponLock, EXPIRETIME);
System.out.printf("续命"+ b);
}
}
});
//执行业务
businessWork();
} finally {
//无论成功失败都取解锁
boolean b = redisLock.releaseLock(couponLock);
System.out.printf("解锁"+ b);
}
} else {
//加锁失败 重试 自旋
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getRedisLock();
}
}
public void businessWork(){
System.out.printf("这里执行业务代码!");
}
}
redis工具类
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisCommands; import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit; /**
* @author yuerli
* @Date 2020/7/7
* 通过给set NX(一个有锁,其他线程不能再获取锁),PX(设置锁的自动过期时间) 进行保证redis的值以及过期时间的原子性
* 通过给锁设置一个拥有者的标识,即每次在获取锁的时候,生成一个随机不唯一的串放入当前线程,释放锁的时候先去判断对应的值是否和线程中的值相同(使用lua脚本)
* 避免删除了其他锁
*/ @Component
public class RedisLock {
private RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); private String UNLOCK_LUA; private ThreadLocal<String> lockFlag = new ThreadLocal<String>(); @Autowired
public RedisLock(RedisTemplate<Object, Object> redisTemplate)
{
// 通过Lua脚本来达到释放锁的原子性
if("".equals(this.UNLOCK_LUA) || this.UNLOCK_LUA==null )
{
StringBuilder sb = new StringBuilder();
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
sb.append("then ");
sb.append(" return redis.call(\"del\",KEYS[1]) ");
sb.append("else ");
sb.append(" return 0 ");
sb.append("end ");
this.UNLOCK_LUA = sb.toString();
}
this.redisTemplate=redisTemplate;
} public boolean lock(String key, long expire, int retryTimes, long sleepMillis) {
boolean result = setRedis(key, expire);
// 如果获取锁失败,按照传入的重试次数进行重试
while((!result) && retryTimes--> 0){
try {
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
return false;
}
result = setRedis(key, expire);
}
return result;
} private boolean setRedis(String key, long expire) {
//为了保证设置锁和过期时间的两个操作原子性 spring data 的 RedisTemplate当中没有这样的方法,但是jedis当中有这样的原子操作的方法
//需要通过RedisTemplate的execute方法获取jedis里操作命令对象
// NX:表示只有当锁定资源不存在的时候才能set成功。利用Redis的原子性,保证了只有第一个请求的线程才能获得锁,而后其他线程在锁定资源释放前都不能获取锁
// PX:expire表示锁定的资源的自动过期时间,单位是毫秒。具体过期时间根据实际场景而定。 //通过set NX,PX的命令设置保证了Redis值和自动过期时间的原子性,避免在调用setIfAbsent方法的时候线程挂掉,没有设置过期时间而导致死锁,使得锁不能释放
try {
String result = redisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
JedisCommands commands = (JedisCommands) connection.getNativeConnection();
String uuid = UUID.randomUUID().toString();
lockFlag.set(uuid); // 锁定的资源 return commands.set(key, uuid, "NX", "PX", expire);
}
});
return !StringUtils.isEmpty(result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
return false;
} /*上面的方法通过设置set的NX,PX命令保证了Redis值和自动过期时间的原子性,但是还有一个问题是如果线程T1获取锁,但是在处理T1的业务时候,
由于某些原因阻塞了较长时间,这个时候设定的过期时间到了,线程T2获取了锁,线程T1操作完后释放了锁(释放了T2的锁)
所以也就是说T2的线程上面没有提供锁的保护机制。因此需要给锁定一个拥有者的标识,即每次在获取锁的时候,生成一个随机不唯一的串放入当前线程,
释放锁的时候先去判断对应的值是否和线程中的值相同。*/
public boolean releaseLock(String key) {
// 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
try {
List<String> keys = new ArrayList<String>();
keys.add(key);
List<String> args = new ArrayList<String>();
args.add(lockFlag.get());
// 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
// spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
Long result = redisTemplate.execute(new RedisCallback<Long>() {
public Long doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
// 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
// 集群模式
if (nativeConnection instanceof JedisCluster) {
return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
}
// 单机模式
else if (nativeConnection instanceof Jedis) {
return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
}
return 0L;
}
});
return result != null && result > 0;
} catch (Exception e) {
e.printStackTrace();
}
return false;
} /**
* 获取过期时间
* @param key
* @return
*/
public Long getExpireTime(String key) {
Long expire1 = redisTemplate.getExpire(key);
return expire1;
} /**
* 重新设置过期时间
* @param key
* @return
*/
public boolean setExpireTime(String key, long expire) { Boolean expire1 = redisTemplate.expire(key, expire, TimeUnit.MILLISECONDS);
return expire1;
} /**
* 判断是否存在
* @param key
* @return
*/
public boolean hasExists(String key) {
boolean exists = redisTemplate.hasKey(key);
return exists;
} }
redis做幂等
//利用redis做幂等
//同一个key2秒之内只能触发一次
if(!redisLock.lock("redisKey",2000,0,0) ) {
System.out.printf("请勿重复提交");
}
写的有错的地方,请大家指正,多多包涵!
springBoot实现redis分布式锁的更多相关文章
- SpringBoot集成Redis分布式锁以及Redis缓存
https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...
- spring-boot 中实现标准 redis 分布式锁
一,前言 redis 现在已经成为系统缓存的必备组件,针对缓存读取更新操作,通常我们希望当缓存过期之后能够只有一个请求去更新缓存,其它请求依然使用旧的数据.这就需要用到锁,因为应用服务多数以集群方式部 ...
- redis分布式锁在springboot中的实现
理论知识 redis分布式锁的实现方案请参考文章 如何优雅地用redis实现分布式锁 本案例简介 以秒杀活动为例子,在多线程高并发的情况下需要保证秒杀业务的线程安全性,确保秒杀记录与所扣库存数 ...
- 基于SpringBoot AOP面向切面编程实现Redis分布式锁
基于SpringBoot AOP面向切面编程实现Redis分布式锁 基于SpringBoot AOP面向切面编程实现Redis分布式锁 基于SpringBoot AOP面向切面编程实现Redis分布式 ...
- Redis分布式锁升级版RedLock及SpringBoot实现
分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问,Java中我们一般可以使用synchronized语法和ReetrantLock去保证,这实际上是本地锁的方式.但是现在 ...
- springboot+redis分布式锁-模拟抢单
本篇内容主要讲解的是redis分布式锁,这个在各大厂面试几乎都是必备的,下面结合模拟抢单的场景来使用她:本篇不涉及到的redis环境搭建,快速搭建个人测试环境,这里建议使用docker:本篇内容节点如 ...
- springboot项目:Redis分布式锁的使用(模拟秒杀系统)
模拟秒杀系统: 第一步:编写Service package com.payease.service; /** * liuxiaoming * 2017-12-14 */ public interfac ...
- spring boot redis分布式锁
随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁.分布式锁的实现有很多种,比如基于数据库. zookeeper 等,本文主要介绍使用 Redis 做分布式锁的方式,并封装成spring b ...
- Redis分布式锁的try-with-resources实现
Redis分布式锁的try-with-resources实现 一.简介 在当今这个时代,单体应用(standalone)已经很少了,java提供的synchronized已经不能满足需求,大家自然 而 ...
随机推荐
- 情话爬虫工具[windows版]
有没有在气氛暧昧的情况下想说点什么却又无话可说?女朋友有没有抱怨过你,只会写代码,一点都不懂情调?这次,是时候要改变她对你的看法了!一键爬取情话,情话全都躺在txt里面.想怎么玩就怎么玩!张口一句情话 ...
- React中对render进行的小优化
react中state和props变化会造成render的重新渲染,有时候我们会在render函数中进行一些稍微复杂的逻辑运算 比如说,像下边这种 在props中将 industries引入,然后对其 ...
- Java8新增的这些集合骚操作,你掌握了嘛?
目录 Iterable的forEach Iterator的forEachRemaining Collection的removeIf Stream操作 List的replaceAll List的sort ...
- Hive基础语法5分钟速览
Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的sql查询功能,可以将sql语句转换为MapReduce任务进行运行. 其优点是学习成本低,可以通过 ...
- 网站开发学习Python实现-Django的models学习-生鲜项目(6.3.2)
@ 目录 1.说明 2.模型类的设计 3.代码的具体实现 4.详情地址 关于作者 1.说明 models是django的很重要的部分,所以深入研究. 本文章的所研究项目为黑马教育python课程中的项 ...
- 【Java】Java Win10环境搭建--JDK、Eclipse
win10安装Java JDK环境及Eclipse安装使用(Hello world) win10环境下安装Java环境,对于小白来说简直是头疼,因为Java内部环境有着JDK和JRE两块,互相牵扯着很 ...
- 百度Echarts中国地图经纬度
百度显示中国地图的地址 https://www.makeapie.com/explore.html#sort=rank~timeframe=all~author=all%3Ftdsourcetag v ...
- Vue2+Koa2+Typescript前后端框架教程--03后端路由和三层模式配置
昨天将Koa2的基础框架和自动编译调试重启服务完成,今天开始配置路由和搭建基础的三层架构模式. 路由中间件:koa-router,即路由导航,就是我们平时使用最广泛的get/post方法执行的URL路 ...
- IDEA使用SVN上传项目
文章最后附上svn服务器和客户端下载地址 一.IDEA集成SVN 二.查看SVN仓库 调出svn视图: 连接svn服务器: 连接后效果如下: 忽略上传文件 忽略文件如下:可以选择按规则匹配 .idea ...
- 【探索之路】机器人篇(1)-ROS-mwRobot开篇
机器人的定义 首先,什么才是机器人?机器人是不是必须和人一样的外形?我们先看一看维基百科给机器人的定义: 由上可见,机器人并不是和人一样的外形,而是可以模拟人类行为或者思想再或者是模拟其他生物的机械. ...