Redis實現分佈式鎖

以前咱們使用的定時任務都是隻部署在了單臺機器上,爲了解決單點的問題,爲了保證一個任務,只被一臺機器執行,就須要考慮鎖的問題,因而就花時間研究了這個問題。到底怎樣實現一個分佈式鎖呢?redis

鎖的本質就是互斥,保證任什麼時候候能有一個客戶端持有同一個鎖,若是考慮使用redis來實現一個分佈式鎖,最簡單的方案就是在實例裏面建立一個鍵值,釋放鎖的時候,將鍵值刪除。可是一個可靠完善的分佈式鎖須要考慮的細節比較多,咱們就來看看如何寫一個正確的分佈式鎖。算法

單機版分佈式鎖 SETNX

因此咱們直接基於 redis 的 setNX (SET if Not eXists)命令,實現一個簡單的鎖。直接上僞碼安全

鎖的獲取:網絡

SET resource_name my_random_value NX PX 30000複製代碼

鎖的釋放:架構

if redis.call("get",KEYS[1]) == ARGV[1] then
        return redis.call("del",KEYS[1])
    else
        return 0
    end複製代碼

幾個細節須要注意:併發

  • 首先在獲取鎖的時候咱們須要設置設置超時時間。設置超時時間是爲了,防止客戶端崩潰,或者網絡出現問題之後鎖一直被持有。真個系統就死鎖了。
  • 使用 setNX 命令,保證查詢和寫入兩個步驟是原子的
  • 在鎖釋放的時候咱們判斷了KEYS[1]) == ARGV[1],在這裏 KEYS[1]是從redis裏面取出來的value,ARGV[1]是上文生成的my_random_value。之因此進行以上的判斷,是爲了保證鎖被鎖的持有者釋放。咱們假設不進行這一步校驗:dom

    1. 客戶端A獲取鎖,後發線程掛起了。時間大於鎖的過時時間。
    2. 鎖過時後,客戶端B獲取鎖。
    3. 客戶端A恢復之後,處理完相關事件,向redis發起 del命令。鎖被釋放
    4. 客戶端C獲取鎖。這個時候一個系統中同時兩個客戶端持有鎖。異步

      形成這個問題的關鍵,在於客戶端B持有的鎖,被客戶端A釋放了。分佈式

  • 鎖的釋放必須使用lua腳本,保證操做的原子性。鎖的釋放包含了get,判斷,del三個步驟。若是不能保證三個步驟的原子性,分佈式鎖就會有併發問題。性能

注意了以上細節,一個單redis節點的分佈式鎖就達成了。

在這個分佈式鎖中仍是存在一個單點的redis。也許你會說,Redis是 master-slave的架構,發生故障的時候切換到slave就好,可是Redis的複製是異步的。

  • 若是在客戶端A在master上拿到了鎖。
  • 在master將數據同步到slave上以前,master宕機。
  • 客戶端B就從slave上又一次拿到了鎖。

這樣因爲Master的宕機,形成了同時多人持有鎖。若是你的系統可用接受短時時間內,有多人持有鎖。這個簡單的方案就能解決問題。

可是若是解決這個問題。Redis的官方提供了一個Redlock的解決方案。

第二個實現 RedLock

爲了解決,Redis單點的問題。Redis的做者提出了RedLock的解決方案。方案很是的巧妙和簡潔。
RedLock的核心思想就是,同時使用多個Redis Master來冗餘,且這些節點都是徹底的獨立的,也不須要對這些節點之間的數據進行同步。

假設咱們有N個Redis節點,N應該是一個大於2的奇數。RedLock的實現步驟:

  1. 取得當前時間
  2. 使用上文提到的方法依次獲取N個節點的Redis鎖。
  3. 若是獲取到的鎖的數量大於 (N/2+1)個,且獲取的時間小於鎖的有效時間(lock validity time)就認爲獲取到了一個有效的鎖。鎖自動釋放時間就是最初的鎖釋放時間減去以前獲取鎖所消耗的時間。
  4. 若是獲取鎖的數量小於 (N/2+1),或者在鎖的有效時間(lock validity time)內沒有獲取到足夠的說,就認爲獲取鎖失敗。這個時候須要向全部節點發送釋放鎖的消息。

對於釋放鎖的實現就很簡單了。想全部的Redis節點發起釋放的操做,不管以前是否獲取鎖成功。

同時須要注意幾個細節:

  • 重試獲取鎖的間隔時間應當是一個隨機範圍而非一個固定時間。這樣能夠防止,多客戶端同時一塊兒向Redis集羣發送獲取鎖的操做,避免同時競爭。同時獲取相同數量鎖的狀況。(雖然機率很低)
  • 若是某master節點故障以後,回覆的時間間隔應當大於鎖的有效時間。

    1. 假設有A,B,C三個Redis節點。
    2. 客戶端foo獲取到了A、B兩個鎖。
    3. 這個時候B宕機,全部內存的數據丟失。
    4. B節點回復。
    5. 這個時候客戶端bar從新獲取鎖,獲取到B,C兩個節點。
    6. 此時又有兩個客戶端獲取到鎖了。

      因此若是恢復的時間將大於鎖的有效時間,就能夠避免以上狀況發生。同時若是性能要求不高,甚至能夠開啓Redis的持久化選項。

總結

瞭解了Redis分佈式的實現之後,其實以爲大多數的分佈式系統其實原理很簡單,可是爲了保證分佈式系統的可靠性須要注意不少的細節,瑣碎異常。
RedLock算法實現的分佈式鎖就是簡單高效,思路至關巧妙。
可是RedLock就必定安全麼?我還會寫一篇文章來討論這個問題。敬請你們期待,文章地址

相關文章
相關標籤/搜索