业务场景: 程序中有个一分钟执行一次的策略遍历与判断的任务,为了防止多台机器重复执行,需要设置分布式锁
查询资料,这个技术方案比较成熟
1.使用数据库 2.使用redis 3.使用zk
下面介绍下单机redis的使用分布式锁的思路,整体思路较为完善,其他资料较残缺
注意点:
1.设置随机value值 ,防止A释放锁出现释放了其他客户端B在使用的锁的问题
2.设置失效时间 ,防止死锁
3.Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行
set lock:codehole true ex 5 nx
4,释放锁在finally中执行 ,确保锁得到释放
5.删除锁要原子性,原因看附属文第一篇
//只有当key不存在时,才设置指定key的值,返回true; 否则不设置,返回false,如果key是新key,则设置超时时间(以秒为单位
boolean setnx = redisUtils.setnx(KEY_PRE + minute, value, 30 * 60);
redisUtils中的方法封装:
private JedisCluster jedisCluster;
private Pool<Jedis> jedisPool;
public boolean setnx(String key, String value, int expireSeconds) {
if (this.isCluster) {
return "OK".equals(this.jedisCluster.set(key, value, "NX", "EX", (long)expireSeconds));
} else {
Jedis jedis = (Jedis)this.jedisPool.getResource();
Throwable var5 = null;
boolean var6;
try {
var6 = "OK".equals(jedis.set(key, value, "NX", "EX", expireSeconds));
} catch (Throwable var15) {
var5 = var15;
throw var15;
} finally {
if (jedis != null) {
if (var5 != null) {
try {
jedis.close();
} catch (Throwable var14) {
var5.addSuppressed(var14);
}
} else {
jedis.close();
}
}
}
return var6;
}
}
}
上述sebnx 与exp在代码里也并不是原子性,稍晚点修改 其实和上面思路相同
我的业务中并没有这样使用.因为如果三台主机时间不同步,前后差十几秒或者几分钟,同样不可避免的本来这一分钟的已经被执行了,锁也释放了,但是另一台机器稍晚点才到达这个时间节点,也会执行,不可避免重复写入的问题,
解决方案:在redis 中设置key-value但不删除,保存半小时,每一分钟生成一个唯一,生成了这一分钟就会只有一个机器去执行
try
//每分钟生成一个key,保存30分钟,避免服务器之间时间差异产生重复写入的问题
//只有当key不存在时,才设置指定key的值,返回true; 否则不设置,返回false,如果key是新key,则设置超时时间(以秒为单位
if (getLock()) {
return;
}
private boolean getLock() {
int minute = getMinute();
String value = fetchLockValue();
boolean setnx = redisUtils.setnx(KEY_PRE + minute, value, 30 * 60);
log.info("Redis Lock key : " + KEY_PRE + minute + ",value : " + value);
//获取不到锁,退出
if (!setnx) {
return true;
}
log.info("获取到锁:{}", KEY_PRE + minute);
return false;
}
/**
* @return
*/
private String fetchLockValue() {
return UUID.randomUUID().toString();
}
/**
* 获取当前分钟
*
* @return
*/
private int getMinute() {
Date date = DateUtil.getDate();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar.get(Calendar.MINUTE);
}
附 几篇比较好的帖子
在 Java 中利用 redis 实现一个分布式锁服务
http://www.sohu.com/a/214257481_714863
几种分布式锁的实现方式
https://juejin.im/post/5cff593c6fb9a07ec56e6ed4
基于AOP和Redis实现的简易版分布式锁
https://juejin.im/post/5c0ce11351882505d8406abc
80% 人不知道的 Redis 分布式锁的正确实现方式(Java 版)靠谱推荐
https://blog.csdn.net/X5fnncxzq4/article/details/92857006