在微服务中经常需要使用分布式锁,来执行一些任务。例如定期删除过期数据,在多个服务中只需要一个去执行即可。

以下说明非严格意义的分布式锁,因为 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 实现分布式锁的更多相关文章

  1. Springboot中使用Redisson实现分布式锁

    1. 概述 老话说的好:便宜没好货,有价值的商品,即使再贵,也有人会买. 言归正传,今天继续讨论有关"锁"的话题,synchronized 和 ReentrantLock 大家应该 ...

  2. 基于单机redis的分布式锁实现

    最近我们有个服务经常出现存储的数据出现重复,首先上一个系统流程图: 用户通过http请求可以通知任务中心结束掉自己发送的任务,这时候任务中心会通过MQ通知结束服务去结束任务保存数据,由于任务结束数据计 ...

  3. 基于redis的分布式锁(转)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  4. 基于redis的分布式锁(不适合用于生产环境)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  5. redis系列:基于redis的分布式锁

    一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

  6. SpringBoot电商项目实战 — Redis实现分布式锁

    最近有小伙伴发消息说,在Springboot系列文第二篇,zookeeper是不是漏掉了?关于这个问题,其实我在写第二篇的时候已经考虑过,但基于本次系列文章是实战练习,在项目里你能看到Zookeepe ...

  7. Redis学习笔记1 -- 单机环境时分布式锁的使用

    使用第三方开源组件Jedis实现Redis客户端,且只考虑Redis服务端单机部署的场景. 前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKee ...

  8. 单机Redis实现分布式互斥锁

    代码地址如下:http://www.demodashi.com/demo/12520.html 0.准备工作 0-1 运行环境 jdk1.8 gradle 一个能支持以上两者的代码编辑器,作者使用的是 ...

  9. java中redis的分布式锁工具类

    使用方式 try { if(PublicLock.getLock(lockKey)){ //这里写代码逻辑,执行完后需要释放锁 PublicLock.freeLock(lockKey); } } ca ...

随机推荐

  1. Python 的直接赋值、Deepcopy、Copy的区别

    直接赋值: 其实就是对象的引用 浅拷贝(copy): 只拷贝符对象,不会拷贝对象内部的子对象 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象 有图有 ...

  2. win10常用快捷键总结

    前言: 很多快捷键在不同版本系统基本相同的,但是今天推送的这篇文章更多的介绍 win10快捷键,微软也是大力推广 旗舰系统 win10 ,所以大家提前升级,提前学习还是有必要的.毕竟2020年微软会放 ...

  3. 笔记-twisted源码-import reactor解析

    笔记-twisted源码-import reactor解析 1.      twisted源码解析-1 twisted reactor实现原理: 第一步: from twisted.internet ...

  4. 怎么HTML表格中的所有字体居中?

    一开始,我在table标签里加入align="center"  发现没什么用.... 后来在css里加入,就可以了 成果如图:

  5. 树莓派4B踩坑指南 - (3)无显示器连接

    无显示器连接 WiFi:如果是原装系统,直接修改wpa_supplicant.conf文件后,放入boot即可(一定注意ssid名称不要写错!!惨痛教训T^T) SSH:在boot盘下新建一个 SSH ...

  6. 调用天气预报webservice接口

    1.将 服务端的wsdl文档保存至 本地 http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?WSDL 2.将里面的 <s:element re ...

  7. Nodejs回调加超时限制两种实现方法

    odejs回调加超时限制两种实现方法 Nodejs下的IO操作都是异步的,有时候异步请求返回太慢,不想无限等待回调怎么办呢?我们可以给回调函数加一个超时限制,到一定时间还没有回调就表示失败,继续后面的 ...

  8. 吴裕雄--天生自然HADOOP操作实验学习笔记:协同过滤算法

    实验目的 初步认识推荐系统 学会用mapreduce实现复杂的算法 学会系统过滤算法的基本步骤 实验原理 前面我们说过了qq的好友推荐,其实推荐算法是所有机器学习算法中最重要.最基础.最复杂的算法,一 ...

  9. 以及Hibernate和Mybatis区别

    ORM框架? Object Relational Mapping,对象-关系映射. 项目中的业务实体有两种表现形式:对象和关系数据,即在内存中表现为对象,在数据库中表现为关系数据. 为什么需要ORM框 ...

  10. Raspbian设置静态ip

    Raspbian static ip 最近入手了树莓派4b,并更具官方教程安装了Raspbian.由于直接通过wifi连接,每次ip跳来跳去很不方便,于是便想着设置静态ip. 由于Raspbian本身 ...