日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

分布式锁双重防死锁演进

發(fā)布時(shí)間:2024/4/13 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 分布式锁双重防死锁演进 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
我們?cè)O(shè)置的分布式鎖,value是一個(gè)時(shí)間戳,但是并沒(méi)有利用起來(lái),但是V3要利用起來(lái)了,先把分布式鎖的超時(shí)時(shí)間拿過(guò)來(lái)/*** 防死鎖之分布式鎖* @throws InterruptedException*/ // @Scheduled(cron="0 */1 * * * ?")//每1分鐘(每個(gè)1分鐘的整數(shù)倍)public void closeOrderTaskV3() throws InterruptedException {//防死鎖分布式鎖long lockTimeout = Long.parseLong(PropertiesUtil.getProperty("lock.timeout","50000"));//鎖50秒有效期//項(xiàng)目由于歷史數(shù)據(jù)關(guān)單訂單比較多,需要處理,初次用50s時(shí)間,后續(xù)改成5s即可.同時(shí)50s也為了講課debug的時(shí)候時(shí)間長(zhǎng)而設(shè)置。//大家可以根據(jù)實(shí)際情況,如果歷史訂單都處理完畢,或者在外部進(jìn)行洗數(shù)據(jù)ok,這里的lock的時(shí)間應(yīng)該設(shè)置小一些,例如1s 2s 3s 4s 5s就足夠啦。//這個(gè)時(shí)間如何用呢,看下面。和時(shí)間戳結(jié)合起來(lái)用。Long setnxResult = RedisShardedPoolUtil.setnx(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK, String.valueOf(System.currentTimeMillis()+lockTimeout));if(setnxResult != null && setnxResult.intValue() == 1){//如果返回值是1,代表設(shè)置成功,獲取鎖closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);}else{//如果setnxResult==null 或 setnxResult.intValue() ==0 即 != 1的時(shí)候//未獲取到鎖,繼續(xù)判斷,判斷時(shí)間戳,看是否可以重置獲取到鎖String lockValueStr = RedisShardedPoolUtil.get(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);//如果lockValue不是空,并且當(dāng)前時(shí)間大于鎖的有效期,說(shuō)明之前的lock的時(shí)間已超時(shí),執(zhí)行g(shù)etset命令.if(lockValueStr != null && System.currentTimeMillis() > Long.parseLong(lockValueStr)){String getSetResult = RedisShardedPoolUtil.getSet(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,String.valueOf(System.currentTimeMillis()+lockTimeout));//再次用當(dāng)前時(shí)間戳getset,//返回給定 key 的舊值。 ->舊值判斷,是否可以獲取鎖// 當(dāng) key 沒(méi)有舊值時(shí),即 key 不存在時(shí),返回 nil 。 ->獲取鎖//這里我們set了一個(gè)新的value值,獲取舊的值。if(getSetResult == null || (getSetResult !=null && StringUtils.equals(lockValueStr,getSetResult))){//獲取到鎖closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);}else{log.info("沒(méi)有獲得分布式鎖:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);}}else{log.info("沒(méi)有獲得分布式鎖:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);}}}然后同樣的會(huì)調(diào)用setnx來(lái)設(shè)置這個(gè)分布式鎖,他的value也是當(dāng)前的時(shí)間,加上分布式鎖的一個(gè)超時(shí)時(shí)間,如果成功,那就關(guān)閉這個(gè)訂單,主要看一下else,else代表沒(méi)有獲得分布式鎖,也就是說(shuō)這個(gè)值是存在的,然后才會(huì)走到else里邊,未獲得到鎖,繼續(xù)判斷,判斷時(shí)間戳,看是否重置并獲取到鎖,這里就要把這個(gè)value用上了,首先我們拿到鎖的value值,這里面就要做一個(gè)判斷了,如果里面的value不等于空,也就是最開(kāi)始設(shè)置一個(gè)分布式鎖,而這個(gè)鎖的時(shí)間是5秒,也就是當(dāng)前的時(shí)間加上lockTimeOut,走到了這里,未獲取到鎖,我們?cè)侔堰@個(gè)鎖的value拿出來(lái),如果他不等于空,并且當(dāng)前的時(shí)間已經(jīng)大于設(shè)置鎖的時(shí)候設(shè)置的vaule值,那其實(shí)就可以代表這個(gè)鎖是失效的了,我們認(rèn)為這個(gè)value值這個(gè)時(shí)間,當(dāng)前時(shí)間加上lockTimeout,如果超過(guò)這個(gè)時(shí)間,其實(shí)我是有權(quán)利,即使我這個(gè)鎖還沒(méi)有設(shè)置,他的有效期,即使你這個(gè)key是永久的,ttl返回-1的話,我們?cè)僦匦聅et一個(gè)值,因?yàn)樽叩絠f里面,我可以來(lái)獲取鎖,那如果我滿足獲取鎖的條件,我要重新設(shè)置這個(gè)鎖,那再重新設(shè)置鎖,我還要把舊的值返回回來(lái),所以我們要到Util里面封裝一個(gè)方法,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就是說(shuō),我設(shè)置一個(gè)新值,但是我要立刻拿到返回值,而不是我先get一個(gè)值,getset是具有原子性的,也就是說(shuō)我在set的同時(shí),把舊的值返回回來(lái),如果我們get再set是不具有原子性的,getset方法我們封裝好了,這里是在set一個(gè)新值的時(shí)候返回一個(gè)舊值,也就是我們執(zhí)行完這條命令,分布式鎖的時(shí)間會(huì)被重置,以現(xiàn)在的時(shí)間加上locktimeout,來(lái)重置這個(gè)value,同時(shí)把舊的值,是因?yàn)槲覀兪莟omcat集群,另外一個(gè)應(yīng)用這個(gè)值已經(jīng)被改變了,所以我們不能用這個(gè)值來(lái)做判斷了,我們要拿到最新的一個(gè)舊值是多少,如果我們是一個(gè)tomcat,getSetResult和lockTimeout這個(gè)值是相等,但是我們是多個(gè)tomcat,是有可能不想等的,因?yàn)橛卸鄠€(gè)進(jìn)程在執(zhí)行這個(gè)任務(wù),getset返回值是這樣的,當(dāng)key沒(méi)有舊值時(shí),即key不存在的時(shí)候,返回nil,這個(gè)是官方的文檔,nil在JAVA Jedis就是一個(gè)null,獲取鎖,目的都是為了獲取鎖,那也就是說(shuō),我在getset的時(shí)候,我set了一個(gè)新值,但是我發(fā)現(xiàn)并沒(méi)有拿到老的值,代表這個(gè)鎖在redis里面已經(jīng)不存在了,所以我就可以直接獲取鎖直接操作了,我們set了一個(gè)新的value值,獲取舊的值,如果getset的結(jié)果是一個(gè)空,這個(gè)值set了,get的時(shí)候那不到,在set之前這個(gè)鎖已經(jīng)消失了,消失了返回的就是null,這種情況下我們會(huì)獲取鎖,哪還有一種情況是什么呢,StringUtils.equals(lockValueStr,getSetResult)),如果他兩個(gè)相等,就代表著,代碼執(zhí)行到這里,這個(gè)值沒(méi)有被其他的進(jìn)程set,代表我拿到的鎖是安全的,并且我也有權(quán)利去獲取這個(gè)鎖,首先我在這一行重新拿了這個(gè)值,然后在if里面判斷了一下,當(dāng)前的時(shí)間已經(jīng)超過(guò)原來(lái)設(shè)置的時(shí)間,再加上鎖的超時(shí)時(shí)間,那進(jìn)入if的就代表,我可以有獲取鎖的權(quán)利,那然后我再set一個(gè)值,但是我要拿到舊的值,如果他們相同,就代表真正獲取到鎖,那在這里的時(shí)候就可以真正執(zhí)行,如果里面的value拿到之后,和現(xiàn)在的時(shí)間對(duì)比,發(fā)現(xiàn)并沒(méi)有大于之前設(shè)置的分布式鎖的value,那就代表著老的鎖并沒(méi)有失效,是還在使用的,都是沒(méi)有獲取到分布式鎖,回想我們之前講的分布式鎖的原理的時(shí)候,我們有一個(gè)流程圖,我們一起來(lái)回想一下,這樣理解的就更深入,剛剛V2鎖的缺陷已經(jīng)說(shuō)了,我們把同樣的場(chǎng)景放在V3說(shuō)一下,假設(shè)這個(gè)鎖一直設(shè)置在這里邊,沒(méi)有被expire,這個(gè)鎖就一直存在reids當(dāng)中了,那么下次我們進(jìn)入task的時(shí)候,因?yàn)間etnx會(huì)失敗,這個(gè)時(shí)候我們把里面的value值拿出來(lái)了,因?yàn)橹暗膌ocktimeout是50秒,假設(shè)現(xiàn)在已經(jīng)過(guò)了一分鐘,也就是說(shuō)過(guò)了60秒,他肯定是不等于空的,因?yàn)檫@個(gè)鎖存在才會(huì)走到else里邊,并且現(xiàn)在的時(shí)間,老的鎖是鎖了50秒,而現(xiàn)在剛好過(guò)了60秒,超過(guò)50秒了,所以當(dāng)前的時(shí)間會(huì)大于鎖的一個(gè)value,然后getset,既然我要重新獲取鎖,我要使用之前,我就把他的時(shí)間重置,把它的value重置,所以我把他的value重置成新的時(shí)間,之前拿了一個(gè)舊值,現(xiàn)在又拿了一個(gè)舊值,這個(gè)舊值又不等于空,如果舊值等于空,就代表我們?cè)谧遡f的時(shí)候,另外一個(gè)進(jìn)程已經(jīng)把這個(gè)鎖刪除了,所以我拿不到老值,也沒(méi)有關(guān)系,所以等于空的時(shí)候也會(huì)進(jìn)來(lái),接著說(shuō)不等于空,不等于空接著走,不等于空的時(shí)候,和之前的老的值,是否是一樣呢,如果一樣就代表著,我們這個(gè)鎖并沒(méi)有被其他進(jìn)程獲取到,代表著我可以用它了,在這里才是真正獲取到鎖,我就開(kāi)始執(zhí)行closeOrder,else肯定是沒(méi)有獲取到鎖,這個(gè)else也很好理解,我們這個(gè)鎖是50秒,關(guān)閉了,重啟好了,所以當(dāng)前的時(shí)間并不會(huì)大于這個(gè),因?yàn)楫?dāng)前的時(shí)間并不會(huì)大于這個(gè),并沒(méi)有獲取鎖的一個(gè)權(quán)力,那其實(shí)在這個(gè)過(guò)程中,有可能會(huì)浪費(fèi)一些時(shí)間,所以說(shuō)了,我們的locktimeout,不易設(shè)置過(guò)長(zhǎng),比如呢,現(xiàn)在是50秒,后續(xù)我們會(huì)把配置文件改成5秒,那我們現(xiàn)在就改一下吧,把這個(gè)鎖配置改成5秒,也就是說(shuō)我這個(gè)timeout只用5秒,其實(shí)在expire的時(shí)候只用5秒就OK了,那如果5秒你的鎖還沒(méi)有執(zhí)行完你的任務(wù)的話,那我們可以把這個(gè)5秒的時(shí)間調(diào)大一些,避免了分布式鎖出現(xiàn)問(wèn)題的時(shí)候,占用的時(shí)間太長(zhǎng),那現(xiàn)在這種情況呢,就不會(huì)發(fā)生死鎖問(wèn)題了,在closeOrder里面有一個(gè)expire,同時(shí)即使expire沒(méi)有執(zhí)行過(guò),我們也會(huì)通過(guò)里面的value值,進(jìn)行一個(gè)判斷,同時(shí)使用兩個(gè)關(guān)鍵的方法,getset,和setnx,那分布式鎖也是分布式項(xiàng)目的一個(gè)重點(diǎn),希望可以把這個(gè)流程寫(xiě)下來(lái),可以獨(dú)自把這個(gè)流程圖畫(huà)出來(lái),這樣對(duì)你的理解是非常有好處的,我們這個(gè)定時(shí)任務(wù)每次,只有一個(gè)來(lái)執(zhí)行,并且分布式鎖是所有TOMCAT集群中的各個(gè)引用共享的,他們之間來(lái)競(jìng)爭(zhēng)這個(gè)分布式鎖,來(lái)保證我們這個(gè)定時(shí)任務(wù),每次只有一個(gè)服務(wù)來(lái)進(jìn)行,這個(gè)非常重要,因?yàn)槲覀冊(cè)趯?xiě)代碼的時(shí)候,不可能說(shuō),我寫(xiě)了一個(gè)項(xiàng)目,當(dāng)然Spring Schedule也可以放到xml里面來(lái)配置

?

總結(jié)

以上是生活随笔為你收集整理的分布式锁双重防死锁演进的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。