Redis實現分布式鎖


布式鎖實現的三個核心要素:加鎖、解鎖、鎖超時。

一、Redis實現分布式鎖基本原理

1.1、Redis具體實現

情景:存在多台JVM需要同時對某一商品(id)進行操作。

  • 加鎖:使用setnx命令,偽代碼:setnx(id,value)
      返回1,說明key不存在,線程搶鎖成功;
      返回1,說明key已存在,線程搶鎖失敗。
     注意:setnx(id,value)中key為操作商品的id,value值可用於防止誤刪鎖,下文有提到。

  • 解鎖:使用del命令,偽代碼:del(id)

  • 鎖超時:如果一個得到鎖的線程在執行任務的過程中掛掉,來不及顯式地釋放鎖,該資源將會被永遠占用,其他線程將無法訪問。
       可以使用expire為key設置一個超時時間,與setnx命令一起執行(setnx不支持超時參數),用以保證即使未被顯式釋放,該鎖也可在一定時間后自動釋放。偽代碼:expire(key, 30)。

1.2、上述實現存在的問題

  • 非原子性操作
      加鎖setnx和鎖超時expire兩個命令未非原子性操作,當執行加鎖setnx后,若因網絡或客戶端問題鎖超時expire命令未成功執行時,鎖將無法被釋放。
    解決方案:
      使用set命令取代setnx和expire命令。setnx本身不支持設置超時時間。在Redis 2.6.12以上版本為set指令增加了可選參數,偽代碼:set(key, value, expire)。

  • 誤刪鎖
    設想如下情形:
      (1)JVM1使用set(001, 002, 30)成功獲取鎖,並設置超時時間為30s;
      (2)JVM1開始數據處理,處理時間已經超過了30s...
      (3)服務器檢測到(001, 002, 30)數據超時,將自動執行del進行數據刪除,此時JVM1還在數據處理...
      (4)此時,JVM2使用set(001, 002, 30)成功獲取鎖,並設置超時時間為30s;
      (5)JVM2開始數據處理。與此同時,JVM1處理完成,操作提交后,根據商品id001,執行了del;
        到此,JVM1成功誤刪了JVM2的鎖。
    解決方案:
      del數據之前,增加鎖判斷機制:判斷要刪除的鎖是否屬於本線程。操作流程:
      (1)加鎖:set(id, threadId,expire),其中value為當前線程ID;
      (2)解鎖:執行del命令時,根據id和threadId數據判斷該鎖是否仍屬於本線程。是,則刪除。

  • 並發問題
      基於誤刪鎖的前提下,由於我們無法確定程序成功處理完成數據的具體時間,這就為超時時間的設置提出了難題。設置時間過長、過短都將影響程序並發的效率。
    解決方案:JVM1需要自己判斷在超時時間內是否完成數據處理,如未完成,應請求延長超時時間。具體操作:
      為獲取鎖的鎖的線程開啟一個守護線程。當29秒時(或更早),線程A還沒執行完,守護線程會執行expire指令,為這把鎖“續命”20秒。守護線程從第29秒開始執行,每20秒執行一次。當線程A執行完任務,會顯式關掉守護線程。

      另一種情況:如果節點1 忽然斷電,由於線程A和守護線程在同一個進程,守護線程也會停下。當過了超時時間后,沒有守護進程的“續命”,鎖將自動釋放。

  • 作者: DeepInThought
    出處: https://www.cnblogs.com/DeepInThought
    本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

  • 免責聲明!

    本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



     
    粵ICP備18138465號   © 2018-2025 CODEPRJ.COM