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 ...
随机推荐
- python多线程下载ts文件
# -*- coding: utf-8 -*- """ Created on Wed Aug 22 15:56:19 2018 @author: Administrato ...
- nginx 反向代理memcached、websocket及nginx文件方面的优化
安装memcached服务,并启动添加数据 yum -y install memcached systemctl start memcached.service 启动 [root@python ~]# ...
- IP地址规划
IP地址(Internet Protocol Address),缩写为IP Adress,是一种在Internet上的给主机统一编址的地址格式,也称为网络协议(IP协议)地址.它为互联网上的每一个网络 ...
- 线程安全Collections.synchronizedList
ollections.synchronizedList引发的线程安全问题 有些容器是线程安全的(Vector,ConcurrentLinkedQueue等),有些则不是(list等),利用类 似 pr ...
- IDEA中找不到spring的配置文件,或者不存在某个目录(比如没有src 目录)
比如 项目中src目录找不到了,解决方式为: 这类问题都是设置这儿.
- mkvirtualenv: 未找到命令的解决方法
1.升级python包管理工具pip pip install --upgrade pip 备注:当你想升级一个包的时候 `pip install --upgrade 包名` 2.python虚拟环境安 ...
- Spark 写 Hive table 非常慢【解决】
代码如下: dataFrame.createOrReplaceTempView("view_page_utm") val sql = s""" |in ...
- win32下的命令行集合 (最优秀的工具)
HIDECMD.rar下载:以隐藏窗口的方式运行批处理. curl.exe 7.12.2 文件传输 593,670 curl是一个利用URL语法在命令行方式下工作的的文件传输工具 E6ED60CDA8 ...
- 网络协议-restful协议
REST Representational State Transfer, 是一种软件架构风格,提供一系列限制指导,用于更好的创建web service. 符合REST 架构风格的web servic ...
- 吴裕雄 Bootstrap 前端框架开发——Bootstrap 按钮:原始按钮样式(未被操作)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...