概述
我们设置的分布式锁,value是一个时间戳,但是并没有利用起来,但是V3要利用起来了,先把分布式锁的超时时间拿过来
/**
* 防死锁之分布式锁
* @throws InterruptedException
*/
//
@Scheduled(cron="0 */1 * * * ?")//每1分钟(每个1分钟的整数倍)
public void closeOrderTaskV3() throws InterruptedException {
//防死锁分布式锁
long lockTimeout = Long.parseLong(PropertiesUtil.getProperty("lock.timeout","50000"));
//锁50秒有效期
//项目由于历史数据关单订单比较多,需要处理,初次用50s时间,后续改成5s即可.
同时50s也为了讲课debug的时候时间长而设置。
//大家可以根据实际情况,如果历史订单都处理完毕,或者在外部进行洗数据ok,
这里的lock的时间应该设置小一些,例如1s 2s 3s 4s 5s就足够啦。
//这个时间如何用呢,看下面。和时间戳结合起来用。
Long setnxResult = RedisShardedPoolUtil.setnx(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,
String.valueOf(System.currentTimeMillis()+lockTimeout));
if(setnxResult != null && setnxResult.intValue() == 1){
//如果返回值是1,代表设置成功,获取锁
closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}else{
//如果setnxResult==null 或 setnxResult.intValue() ==0 即 != 1的时候
//未获取到锁,继续判断,判断时间戳,看是否可以重置获取到锁
String lockValueStr = RedisShardedPoolUtil.get(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
//如果lockValue不是空,并且当前时间大于锁的有效期,说明之前的lock的时间已超时,执行getset命令.
if(lockValueStr != null && System.currentTimeMillis() > Long.parseLong(lockValueStr)){
String getSetResult = RedisShardedPoolUtil
.getSet(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,
String.valueOf(System.currentTimeMillis()+lockTimeout));
//再次用当前时间戳getset,
//返回给定 key 的旧值。
->旧值判断,是否可以获取锁
// 当 key 没有旧值时,即 key 不存在时,返回 nil 。 ->获取锁
//这里我们set了一个新的value值,获取旧的值。
if(getSetResult == null ||
(getSetResult !=null &&
StringUtils.equals(lockValueStr,getSetResult))){
//获取到锁
closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}else{
log.info("没有获得分布式锁:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}
}else{
log.info("没有获得分布式锁:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}
}
}
然后同样的会调用setnx来设置这个分布式锁,他的value也是当前的时间,加上分布式锁的一个超时时间,如果成功,
那就关闭这个订单,主要看一下else,else代表没有获得分布式锁,也就是说这个值是存在的,然后才会走到else里边,
未获得到锁,继续判断,判断时间戳,看是否重置并获取到锁,这里就要把这个value用上了,首先我们拿到锁的value值,
这里面就要做一个判断了,如果里面的value不等于空,也就是最开始设置一个分布式锁,而这个锁的时间是5秒,也就是当前的
时间加上lockTimeOut,走到了这里,未获取到锁,我们再把这个锁的value拿出来,如果他不等于空,并且当前的时间已经大于
设置锁的时候设置的vaule值,那其实就可以代表这个锁是失效的了,我们认为这个value值这个时间,当前时间加上lockTimeout,
如果超过这个时间,其实我是有权利,即使我这个锁还没有设置,他的有效期,即使你这个key是永久的,ttl返回-1的话,
我们再重新set一个值,因为走到if里面,我可以来获取锁,那如果我满足获取锁的条件,我要重新设置这个锁,那再重新设置锁,
我还要把旧的值返回回来,所以我们要到Util里面封装一个方法,getset,
public static String getSet(String key,String value) {
ShardedJedis jedis = null;
String result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.getSet(key,value);
} catch (Exception e) {
log.error("getset key:{} error", key, e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}
getset就是说,我设置一个新值,但是我要立刻拿到返回值,而不是我先get一个值,getset是具有原子性的,也就是说我在
set的同时,把旧的值返回回来,如果我们get再set是不具有原子性的,getset方法我们封装好了,这里是在set一个新值的
时候返回一个旧值,也就是我们执行完这条命令,分布式锁的时间会被重置,以现在的时间加上locktimeout,来重置这个value,
同时把旧的值,是因为我们是tomcat集群,另外一个应用这个值已经被改变了,所以我们不能用这个值来做判断了,我们要拿到
最新的一个旧值是多少,如果我们是一个tomcat,getSetResult和lockTimeout这个值是相等,但是我们是多个tomcat,是有可能
不想等的,因为有多个进程在执行这个任务,getset返回值是这样的,当key没有旧值时,即key不存在的时候,返回nil,这个是
官方的文档,nil在JAVA Jedis就是一个null,获取锁,目的都是为了获取锁,那也就是说,我在getset的时候,我set了一个新值,
但是我发现并没有拿到老的值,代表这个锁在redis里面已经不存在了,所以我就可以直接获取锁直接操作了,我们set了一个新的
value值,获取旧的值,如果getset的结果是一个空,这个值set了,get的时候那不到,在set之前这个锁已经消失了,消失了返回的
就是null,这种情况下我们会获取锁,哪还有一种情况是什么呢,StringUtils.equals(lockValueStr,getSetResult)),
如果他两个相等,就代表着,代码执行到这里,这个值没有被其他的进程set,代表我拿到的锁是安全的,
并且我也有权利去获取这个锁,首先我在这一行重新拿了这个值,然后在if里面判断了一下,
当前的时间已经超过原来设置的时间,再加上锁的超时时间,那进入if的就代表,
我可以有获取锁的权利,那然后我再set一个值,但是我要拿到旧的值,如果他们相同,就代表真正获取到锁,那在这里的时候就可以
真正执行,如果里面的value拿到之后,和现在的时间对比,发现并没有大于之前设置的分布式锁的value,那就代表着老的锁并没有失效,
是还在使用的,都是没有获取到分布式锁,回想我们之前讲的分布式锁的原理的时候,我们有一个流程图,我们一起来回想一下,
这样理解的就更深入,刚刚V2锁的缺陷已经说了,我们把同样的场景放在V3说一下,假设这个锁一直设置在这里边,没有被
expire,这个锁就一直存在reids当中了,那么下次我们进入task的时候,因为getnx会失败,这个时候我们把里面的value值拿出来了,
因为之前的locktimeout是50秒,假设现在已经过了一分钟,也就是说过了60秒,他肯定是不等于空的,因为这个锁存在才会走到else
里边,并且现在的时间,老的锁是锁了50秒,而现在刚好过了60秒,超过50秒了,所以当前的时间会大于锁的一个value,然后getset,
既然我要重新获取锁,我要使用之前,我就把他的时间重置,把它的value重置,所以我把他的value重置成新的时间,之前拿了一个旧值,
现在又拿了一个旧值,这个旧值又不等于空,如果旧值等于空,就代表我们在走if的时候,另外一个进程已经把这个锁删除了,
所以我拿不到老值,也没有关系,所以等于空的时候也会进来,接着说不等于空,不等于空接着走,不等于空的时候,和之前的
老的值,是否是一样呢,如果一样就代表着,我们这个锁并没有被其他进程获取到,代表着我可以用它了,在这里才是真正获取到锁,
我就开始执行closeOrder,else肯定是没有获取到锁,这个else也很好理解,我们这个锁是50秒,关闭了,重启好了,所以当前的时间并
不会大于这个,因为当前的时间并不会大于这个,并没有获取锁的一个权力,那其实在这个过程中,有可能会浪费一些时间,
所以说了,我们的locktimeout,不易设置过长,比如呢,现在是50秒,后续我们会把配置文件改成5秒,那我们现在就改一下吧,
把这个锁配置改成5秒,也就是说我这个timeout只用5秒,其实在expire的时候只用5秒就OK了,那如果5秒你的锁还没有执行
完你的任务的话,那我们可以把这个5秒的时间调大一些,避免了分布式锁出现问题的时候,占用的时间太长,那现在这种情况呢,
就不会发生死锁问题了,在closeOrder里面有一个expire,同时即使expire没有执行过,我们也会通过里面的value值,
进行一个判断,同时使用两个关键的方法,getset,和setnx,那分布式锁也是分布式项目的一个重点,希望可以把这个流程写下来,
可以独自把这个流程图画出来,这样对你的理解是非常有好处的,我们这个定时任务每次,只有一个来执行,并且分布式锁
是所有TOMCAT集群中的各个引用共享的,他们之间来竞争这个分布式锁,来保证我们这个定时任务,每次只有一个服务来
进行,这个非常重要,因为我们在写代码的时候,不可能说,我写了一个项目,当然Spring Schedule也可以放到xml里面来
配置
最后
以上就是整齐歌曲为你收集整理的分布式锁双重防死锁演进的全部内容,希望文章能够帮你解决分布式锁双重防死锁演进所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复