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 ...
随机推荐
- 3 CSS 定位&浮动&水平对齐&组合选择符&伪类&伪元素
CSS Position(定位):元素的定位与文档流无关 static定位: HTML元素的默认值, 没有定位,元素出现在正常的流中 静态定位的元素不会受到top,bottom,left,right影 ...
- 关于hrf图的做法
要拿matlab 的spm 包功能做 Model specification ,review and estimation specify1st level 第二张图是在建模以后,通过spm中的res ...
- jdbc学习over
这次的代码和之前学习到一般的代码主要就是将一些很常见的操作(建立连接.清除连接)不管做什么操作都需要用到它们,所以将它们单独放到另一个工具类里面去. 用到的术语: 1.事务:https://www.c ...
- 「IOI2014」Wall 砖墙
题目描述 给定一个初始元素为 \(0\) 的数列,以及 \(K\) 次操作: 将区间 \([L, R]\) 中的元素对 \(h\) 取 \(max\) 将区间 \([L, R]\) 中的元素对 \(h ...
- 2020年digitalocean最新优惠码100美元奖励
欧美免备案vps服务器digitalocean我用了四年,创建一台vps速度非常快. 由于中国用户扎堆购买Vultr和Linode线路,导致digitalocean中国用户少,反而更稳定.digita ...
- ClientDataSet.locate报错问题
数据集循环之后如果使用locate定位,需要首先将数据集first
- python对ASC码的加减
一般使用这两个函数 sum = ord('A') //结果为65 ord()函数返回值是int 字符要加' '否则会当作变量来看 sum = chr(65) //结果为A 不是char是chr()
- python-python基础7
一.静态方法 通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法,什么是静态方法呢?其实不难理解,普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类 ...
- ajax Ajax处理下载文件response没有反应
参考:https://blog.csdn.net/wf632856695/article/details/52040034
- eclipse js文件无法保存错误
错误信息如下 Save Failedjdk.nashorn.internal.runtime.ECMAException.getEcmaError()Ljava/lang/Object; 网上多番查找 ...