分布式锁双重防死锁演进
生活随笔
收集整理的這篇文章主要介紹了
分布式锁双重防死锁演进
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
我們設置的分布式鎖,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里面來配置
?
總結
以上是生活随笔為你收集整理的分布式锁双重防死锁演进的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分布式锁编写及调试分析
- 下一篇: Redisson框架快速入门