springboot 中单机 redis 实现分布式锁
2024-10-21 03:04:53
在微服务中经常需要使用分布式锁,来执行一些任务。例如定期删除过期数据,在多个服务中只需要一个去执行即可。
以下说明非严格意义的分布式锁,因为 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
最新文章
- Spark学习(二) -- Spark整体框架
- 【开园 and 计划】
- WEB前端工程师面试题【前端】
- 黑客长期摇号不中";黑";掉北京小客车摇号网
- 自定义UITableViewCell
- 一个简单的ajax对象
- EXchange导出通讯录提取url纯文本
- MySQL (九)
- K:java中序列化的两种方式—Serializable或Externalizable
- 让你的微信小程序具有在线支付功能
- dsu on tree 树上启发式合并 学习笔记
- Docker: docker container常用命令实战(2)-数据持久化
- IJ配置项目的TOMCAT
- Spark之submit任务时的Initial job has not accepted any resources; check your cluster UI to ensure that workers are registered and have sufficient memory
- Centos6.5下VNC Server配置
- win32 进程崩溃时禁止弹出错误对话框
- 第三百八十节,Django+Xadmin打造上线标准的在线教育平台—将所有app下的models数据库表注册到xadmin后台管理
- js字符串转换成数字,数字转换成字符串
- chrome://命令
- struts2.xml中所有constant详解--大全