springboot 中单机 redis 实现分布式锁
在微服务中经常需要使用分布式锁,来执行一些任务。例如定期删除过期数据,在多个服务中只需要一个去执行即可。
以下说明非严格意义的分布式锁,因为 redis 实现严格意义的分布式锁还是比较复杂的,对于日常简单使用使用如下简单方法即可。即偶尔不执行任务不影响业务。
实现要点
1)获得锁、释放锁需要是原子操作。要么获取成功,要么失败。释放要么成功,要么失败
2)任务完成需要自己释放自己的锁,不能释放别人的锁。
3)锁要有过期时间限制,防止任务崩溃没有释放锁,导致其他节点无法获得锁。
4)执行节点超时长时间不释放锁,到下次任务开始执行并行存在的情况
要考虑的风险点
1)获取锁失败,偶尔不执行任务要不影响业务或告警人工干预
2)redis 宕机,导致无法获取锁
方案一:低版本使用 jedis 实现
1 添加依赖,高版本或低版本有些方法可能没有
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.10.2</version>
</dependency>
2 实现方法
@Slf4j
public abstract class AbsDistributeLock { private Jedis jedis; public void initDistributeLock(String ip, int port, Integer database, String password) {
jedis = new Jedis(ip, port);
if (password != null && !password.isEmpty()) {
jedis.auth(password.trim());
}
if (database == null || database < || database > ) {
database = ;
}
jedis.select(database);
} private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX"; private static final Long RELEASE_SUCCESS = 1L; /**
* 具体的任务需要子类去实现
*
* @throws RTException 分布式锁异常
*/
public abstract void taskService() throws RTException; /**
* 任一执行,ANY OF
* 所有节点任意一个执行任务 taskService 即可,没有获得锁的节点不执行任务
*
* @param lockKey lockKey
* @param keyValue keyValue
* @param expireTime 过期时间 ms
*/
public void onlyOneNodeExecute(String lockKey, String keyValue, int expireTime) {
boolean getLock = false;
try {
if ((getLock = getDistributedLock(jedis, lockKey, keyValue, expireTime))) {
taskService();
}
} finally {
if (getLock) {
releaseDistributedLock(jedis, lockKey, keyValue);
}
}
} /**
* 所有串行执行,ALL IN LINE
* 所有节点都必须执行该任务,每次只能一个执行。
*
* @param lockKey lockKey
* @param keyValue keyValue
* @param expireTime 过期时间 ms
*/
public void allNodeExecute(String lockKey, String keyValue, int expireTime) {
try {
while (!(getDistributedLock(jedis, lockKey, keyValue, expireTime))) {
try {
Thread.sleep();
} catch (InterruptedException e) {
log.info(e.getMessage());
}
}
taskService();
} finally {
releaseDistributedLock(jedis, lockKey, keyValue);
}
} /**
* @param jedis 客户端
* @param lockKey key
* @param keyValue key值
* @param expireTime 过期时间,ms
*/
public static boolean getDistributedLock(Jedis jedis, String lockKey, String keyValue, int expireTime) {
String result = jedis.set(lockKey, keyValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
log.info("ip:[{}] get lock:[{}], value:[{}], getLock result:[{}]", IpUtil.getLocalIpAddr(), lockKey, keyValue, result);
return true;
} else {
return false;
}
} public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String keyValue) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(keyValue));
log.info("ip:[{}] release lock:[{}], value:[{}], release result: [{}]", IpUtil.getLocalIpAddr(), lockKey, keyValue, result);
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
} }
方案二:高版本的springboot,使用 lua 脚本执行
1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.2..RELEASE</version>
</dependency>
2 代码实现
@Slf4j
public abstract class AbsDistributeLockLua { private RedisTemplate<String, String> redisTemplate; public AbsDistributeLockLua(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
} /**
* 具体的任务需要子类去实现
*
* @throws RTException 分布式锁异常
*/
public abstract void taskService() throws RTException; /**
* 任一执行,ANY OF
* 所有节点任意一个执行任务 taskService 即可,没有获得锁的节点不执行任务
*
* @param lockKey lockKey
* @param keyValue keyValue
* @param expireTime 过期时间 ms
*/
public void onlyOneNodeExecute(String lockKey, String keyValue, int expireTime) {
boolean getLock = false;
try {
if ((getLock = getDistributeLock(redisTemplate, lockKey, keyValue, expireTime))) {
taskService();
}
} finally {
if (getLock) {
releaseDistributeLock(redisTemplate, lockKey, keyValue);
}
}
} /**
* 所有串行执行,ALL IN LINE
* 所有节点都必须执行该任务,每次只能一个执行。
*
* @param lockKey lockKey
* @param keyValue keyValue
* @param expireTime 过期时间 ms
*/
public void allNodeExecute(String lockKey, String keyValue, int expireTime) {
try {
while (!(getDistributeLock(redisTemplate, lockKey, keyValue, expireTime))) {
try {
Thread.sleep();
} catch (InterruptedException e) {
log.info(e.getMessage());
}
}
taskService();
} finally {
releaseDistributeLock(redisTemplate, lockKey, keyValue);
}
} /**
* 通过lua脚本 加锁并设置过期时间
*
* @param key 锁key值
* @param value 锁value值
* @param expire 过期时间,单位毫秒
* @return true:加锁成功,false:加锁失败
*/
public boolean getDistributeLock(RedisTemplate<String, String> redisTemplate, String key, String value, int expire) {
DefaultRedisScript<String> redisScript = new DefaultRedisScript<String>();
redisScript.setResultType(String.class);
String strScript = "if redis.call('setNx',KEYS[1],ARGV[1])==1 then return redis.call('pexpire',KEYS[1],ARGV[2]) else return 0 end";
redisScript.setScriptText(strScript);
try {
Object result = redisTemplate.execute(redisScript, redisTemplate.getStringSerializer(), redisTemplate.getStringSerializer(), Collections.singletonList(key), value, expire);
System.out.println("redis返回:" + result);
return "".equals("" + result);
} catch (Exception e) {
//可以自己做异常处理
return false;
}
} /**
* 通过lua脚本释放锁
*
* @param key 锁key值
* @param value 锁value值(仅当redis里面的value值和传入的相同时才释放,避免释放其他线程的锁)
* @return true:释放锁成功,false:释放锁失败(可能已过期或者已被释放)
*/
public boolean releaseDistributeLock(RedisTemplate<String, String> redisTemplate, String key, String value) {
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(String.class);
String strScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisScript.setScriptText(strScript);
try {
Object result = redisTemplate.execute(redisScript, redisTemplate.getStringSerializer(), redisTemplate.getStringSerializer(), Collections.singletonList(key), value);
return "".equals("" + result);
} catch (Exception e) {
//可以自己做异常处理
return false;
}
}
}
代码地址:https://github.com/crazyCodeLove/distribute-lock
参考文献:
https://www.cnblogs.com/bcde/p/11132479.html
https://blog.csdn.net/u013985664/article/details/94459529
springboot 中单机 redis 实现分布式锁的更多相关文章
- Springboot中使用Redisson实现分布式锁
1. 概述 老话说的好:便宜没好货,有价值的商品,即使再贵,也有人会买. 言归正传,今天继续讨论有关"锁"的话题,synchronized 和 ReentrantLock 大家应该 ...
- 基于单机redis的分布式锁实现
最近我们有个服务经常出现存储的数据出现重复,首先上一个系统流程图: 用户通过http请求可以通知任务中心结束掉自己发送的任务,这时候任务中心会通过MQ通知结束服务去结束任务保存数据,由于任务结束数据计 ...
- 基于redis的分布式锁(转)
基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
- 基于redis的分布式锁(不适合用于生产环境)
基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
- redis系列:基于redis的分布式锁
一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...
- SpringBoot电商项目实战 — Redis实现分布式锁
最近有小伙伴发消息说,在Springboot系列文第二篇,zookeeper是不是漏掉了?关于这个问题,其实我在写第二篇的时候已经考虑过,但基于本次系列文章是实战练习,在项目里你能看到Zookeepe ...
- Redis学习笔记1 -- 单机环境时分布式锁的使用
使用第三方开源组件Jedis实现Redis客户端,且只考虑Redis服务端单机部署的场景. 前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKee ...
- 单机Redis实现分布式互斥锁
代码地址如下:http://www.demodashi.com/demo/12520.html 0.准备工作 0-1 运行环境 jdk1.8 gradle 一个能支持以上两者的代码编辑器,作者使用的是 ...
- java中redis的分布式锁工具类
使用方式 try { if(PublicLock.getLock(lockKey)){ //这里写代码逻辑,执行完后需要释放锁 PublicLock.freeLock(lockKey); } } ca ...
随机推荐
- HDU 2680 最短路 迪杰斯特拉算法 添加超级源点
Choose the best route Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Ot ...
- SpringBoot报错笔记
异常一: 1.访问所有方法路径都返回一个page: 截图: 出错原因:不知道 解决方法:新建项目 异常二: 提交表单信息报错 原因:映射文件和和表单的提交方式不统一 解决方法:统一方式即可: 错误三: ...
- ElementUI 日期选择器 datepicker 选择范围限制
在使用elementUI中日期选择器时,经常会遇到这样的需求——对可选择的时间范围有一定限制,比如我遇到的就是:只能选择今天以前的一年以内的日期. 查阅官方文档,我们发现它介绍的并不详细,下面我们就来 ...
- C#多态学习总结
面向对象编程三大特点 封装 继承 多态.今天我把自己学习多态的过程进行总结 多态 就是 同一个方法在不同情况下,会表选不同的效果(多个形态).在代码上表现就是 同一个父类对象 赋予不同的子类对象 就 ...
- Python环境搭建-1 Python介绍
Python翻译成汉语是蟒蛇的意思,并且Python的logo也是两条缠绕在一起的蟒蛇的样子,然而Python语言和蟒蛇实际上并没有一毛钱关系. Python语言是由荷兰程序员Guido van Ro ...
- 1.WEB安全概述
一.WEB常见的安全性问题简介 XSS(Cross-Site Scripting):跨站脚本攻击漏洞 CSRF(Cross-site request forgery):跨站请求伪造 文件上传漏洞 SQ ...
- C语言程序编译
原来GCC的含义是GNU C Compiler,当初知识编译C语言,而现在GCC不知编译C语言,除此之外它还支持编译Ada.C++.Java.Object C.Pascal.COBOL.等等许多语言, ...
- WebVR大潮来袭时,前端开发能做些什么
WebVR大潮来袭时,前端开发能做些什么? WebVR即web + VR的体验方式,我们可以戴着头显享受沉浸式的网页,新的API标准让我们可以使用js语言来开发.本文将介绍如何快速开发一个We ...
- Java解析Json字符串--数组或列表
Json示例: [ { "age": 25, "gender": "female", "grades": "三 ...
- Windows密码安全性测试
一.本地管理员密码如何直接提取 1.1直接通过mimikatz读取管理员密码 (不能交互式,不能在webshell下用,图形化界面很好用) 第一条:privilege::debug ...