分布式锁(redis)的应用的坑和解决办法
使用场景:两台服务器集群部署中台对接工程(数据同步工程),每台项目使用spring的定时任务执行,1分钟执行1次扫描。
,扫描推送数据中间表,进行处理。
问题描述:随机会出现部分数据被扫描两次的现象。
问题背景:设计目标:定时扫描执行的代码增加了分布式锁,获取锁之后,将查询到的数据中间表数据的状态由1改成0,此时标识已经在处理了,另一台服务器就不会再去扫描了。
问题分析:分布式锁不生效导致的两台服务器同时执行同一条数据。
关键点:1.待扫描数据转成处理中的过程,是否是独立的
2.分布式锁是否持续有效
经过分析发现如下的写法:
if(CacheUtil.getLock(key,second)){
try{
//扫描待处理数据,并且将数据改成处理中,避免被下个进程扫描到
//处理数据逻辑
}catch(Throwable e){
logger.error(e.getMessage(),e);
}finally{
CacheUtil.unlock(key);
}
}
public static boolean getLock(String key, int expires) {
try {
logger.info("获取锁开始:" + key + " timestamp:" + System.currentTimeMillis());
if (getCache().setnx(key, System.currentTimeMillis())) {
logger.info("获取锁成功:key ==> " + key + " 值为:" + getCache().get(key));
return true;
} else {
long currentValueL = 0L;
try {
String currentValue = getCache().get(key) == null ? "0" : String.valueOf(getCache().get(key));
currentValueL = Long.parseLong(currentValue);
} catch (Exception var5) {
currentValueL = 0L;
}
if (currentValueL < System.currentTimeMillis() - (long)expires) {
logger.info("锁过期,删除旧锁:key ==> " + key + " timestamp:" + System.currentTimeMillis());
unlock(key);//*此处和上面的CacheUtil.unlock(key);逻辑是一样的*
return getCache().setnx(key, System.currentTimeMillis());
} else {
logger.info("获取锁结束:" + key + " timestamp:" + System.currentTimeMillis());
return false;
}
}
} catch (Exception var6) {
logger.error("OH,MY GOD! SOME ERRORS OCCURED! AS FOLLOWS :获取锁异常key ==> " + key, var6);
return false;
}
}
public static void unlock(String key) {
try {
cacheManager.unlock(key);
logger.info("解锁成功 key ==> " + key + " timestamp:" + System.currentTimeMillis());
} catch (Exception var2) {
logger.info("解锁失败 key ==> " + key + " 不存在,或已解锁! timestamp:" + System.currentTimeMillis());
}
}
public void unlock(String key) {
this.del(key);//未做任何处理,直接删除了,所以如果无条件调用它,就会出现暴力删除的动作
}
生产上出现的锁不住场景的流程分析图:
结论:
1.进行定时任务扫描时,最好使用按时间分段扫描的逻辑,起始时间可以使用队列生成,先进先出,保障分布式环境下的取数据唯一性。
2.加锁可以使用数据库枷锁和redis加锁,
若用数据库加锁,可以使用类似select * from xxx where id=xxxx for update来锁住这条数据,那么谁获取到这条数据的锁,那么谁就获得了锁,可以往下走。但是这个的效率很差。
若使用redis加锁,就是用setnx的api进行加锁,即若不存在就设置值,存在就不设置值。谁设置到值,谁就拿到锁。
3.对于解锁的逻辑,不能暴力解锁,必须要判断这个锁是否有效,若无效才可以删除掉,否则就会出现锁失效的情况。