分布式锁编写及调试分析
生活随笔
收集整理的這篇文章主要介紹了
分布式锁编写及调试分析
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
那我們現在開始來寫一個分布式鎖,那這個方法叫就叫V2,第二個版本,/*** 可能出現死鎖,雖然在執行close的時候有防死鎖,但是還是會出現,繼續演進V3*/
// @Scheduled(cron="0 */1 * * * ?")//每1分鐘(每個1分鐘的整數倍)public void closeOrderTaskV2() throws InterruptedException {long lockTimeout = Long.parseLong(PropertiesUtil.getProperty("lock.timeout","5000"));//鎖5秒有效期//這個時間如何用呢,看下面。和時間戳結合起來用。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{log.info("沒有獲得分布式鎖:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);}}我們這個分布式鎖要鎖多久,lockTimeout,鎖的超時時間,實際生產環境肯定不能設置成這么長時間了,我們分布式鎖就鎖5秒,那么這個時間怎么用呢,我們要配合時間戳一起來使用,RedisShardedPoolUtil我們封裝了一個setnx,這個方法和set方法差不多,public static Long setnx(String key, String value) {ShardedJedis jedis = null;Long result = null;try {jedis = RedisShardedPool.getJedis();result = jedis.setnx(key, value);} catch (Exception e) {log.error("setnx key:{} value:{} error", key, value, e);RedisShardedPool.returnBrokenResource(jedis);return result;}RedisShardedPool.returnResource(jedis);return result;}也就是我們要set這個鎖,如果鎖的key不存在的話,他才會設置成功,我們聲明鎖的一個key,public interface REDIS_LOCK{String CLOSE_ORDER_TASK_LOCK = "CLOSE_ORDER_TASK_LOCK"; //關閉訂單分布式鎖}把現在的時間轉換成字符串,現在的毫秒數,再加上lockTimeout,現在的lockTime是50秒,如果設置成功了,就代表我獲取這個鎖了,如果沒有獲取鎖,就打印一個日志,沒有獲得分布式鎖,獲取鎖之后我們要怎么做,我們在下面聲明一個方法,這個方法是什么呢,如果我在這里面直接調用closeOrder,但是他有一個問題,這個鎖并沒有釋放,也就是說我們設置的這個鎖呢,是沒有有效期的,我們拿到的是永久,也就是第一次啟動定時任務的時候,設置上鎖,那以后再執行到這里的時候呢,這里面拿到的肯定是0,因為這個key已經存在,setnx返回的肯定是失敗,那我們這里面就存在了問題,那我們現在來封裝一下,我們要把這個鎖設置一個有效期,那我們寫成private就OK了,因為只在內部調用,private void closeOrder(String lockName){
// expire命令用于給該鎖設定一個過期時間,用于防止線程crash,導致鎖一直有效,從而導致死鎖。RedisShardedPoolUtil.expire(lockName,50);//有效期50秒,防死鎖log.info("獲取{},ThreadName:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,Thread.currentThread().getName());int hour = Integer.parseInt(PropertiesUtil.getProperty("close.order.task.time.hour","2"));iOrderService.closeOrder(hour);RedisShardedPoolUtil.del(lockName);//釋放鎖log.info("釋放{},ThreadName:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,Thread.currentThread().getName());log.info("=============================");}這里面傳一個lockName,鎖的名字,我們要調用expire方法,把這個key要設置一個有效期,50秒,有效期50秒,防止死鎖,也就是我肯定要把這個鎖設置一個有效期的,那即使我們現在設置50秒,也是OK的,當然線上的時候我們要把它改成5秒,這里面也是因為5秒太短,我們還看不到那個鎖,可能就過期了,我在關閉訂單獲取鎖之后,設置一個有效期,在這個鎖失效的時候呢,這個鎖也就釋放了,打印日志,當前線程的名字,代表哪個線程獲取了這個鎖,然后把這個時間拿過來,既然我們執行完了關閉完了訂單,即使我們沒有超過50秒,訂單十分少,那么我們也不能讓他等待,我們要及時的釋放鎖,著呢嗎釋放呢,我們直接來刪除就可以了,把這個lock進行一個刪除,也就是我們要主動釋放這個分布式鎖,我們主要是關注分布式鎖,這個邏輯執不執行都OK,我們分析一下,首先TOMCAT1里面這個定時任務開始啟動,然后他獲取到了分布式鎖,又釋放掉了,TOMCAT2啟動,但是他沒有獲取分布式鎖,然后這個定時任務就結束了,也就是說在調用tast的時候,直接走到else里邊,我們在執行分布式鎖的時候,可以看到,設置完分布式鎖,然后就設置了他的有效期,然后立刻就刪除了,我們看一下redis里面,REDIS也是分布式的,這次通過一致性算法,存到了REDIS里面,6379的端口,那我們看一下他的時間,看到他現在是-1
這個時候設置有效期,這里主要是為了防止死鎖,我們的鎖肯定要有一個有效期的,redis自動來釋放他,因為我們在TOMCAT2里面設置好了,所以這里返回值是0,失敗,可以看到現在都是空的
因為TOMCAT2已經把lock鎖刪除掉,也就是TOMCAT2獲取了分布式鎖,我們看一下現在V2版本有什么問題,這里邊要說的是,當我們setnx成功之后,這個lock已經存到redis里面了,但是我們的lock并沒有有效期,然后tomcat就重啟了,關閉了,tomcat2也重啟關閉了,而這個時候我們在分布式的環境下,這個分布式鎖就發生了死鎖,這個鎖根本就不會釋放,因為還沒有走到expire這個方法里面,前提是說,也就是setnx已經執行完成,這樣我們這個版本的分布式鎖就存在了死鎖的問題,雖然我們在closeOrder里面,這里面設置了有效期,防止死鎖,但是在一定條件下,還是會發生死鎖的,那我們有一種折中的方案,關閉tomcat有兩種方式,一種我們找到tomcat進程,來對他進行一個kill,這種是直接把進程關掉了,那么還有一種溫柔的方式,就是調用tomcat的shutdown方法,那如果調用shutdown方法的話,我們還有一種方式來解決他,在最上邊我們聲明一個方法,delLock方法,刪除鎖,我們把刪除鎖的代碼拿過來,放到這里邊然后我們要加一個注解,@PreDestroy,這個注解是干什么用的呢,也就是說當我們沒有使用kill進程的方式,來關閉tomcat,也就是溫柔的關閉tomcat的時候,使用tomcat的shutdown命令,關閉tomcat,那tomcat容器會調用predestory,也就是說在毀滅之前,再調用這個方法,那么這里面就會執行了,那同樣的也能起到這個效果,但是這種方式也有弊端,如果我這里關閉1千個鎖和1萬個鎖,那我們在shutdown的時候呢,這個時間會非常長,這里面用了一個極限的思維,要關閉的東西非常的多,如果我們直接kill掉tomcat的進程,那這個方法根本不會執行,也就沒有用武之地了,現在來看雖然V2做了分布式鎖,但是在防死鎖方面,是有問題,在一定情況下,就是剛剛說的,還是防不住死鎖的,所以我們分布式鎖,繼續演進,我們來形成V3版本的分布式鎖
?
總結
以上是生活随笔為你收集整理的分布式锁编写及调试分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Schedule关闭订单
- 下一篇: 分布式锁双重防死锁演进