關於Redis分佈式鎖

                                            關於Redis分佈式鎖

參考:https://redis.io/topics/distlock

關於分佈式鎖的一些設計要點:

  1. 安全性保證:互斥,在任何一個時刻,只有一個客戶端能獲取鎖。
  2. 可靠性A:不會發生死鎖,最終會獲取到鎖,即使持有鎖的客戶端宕機。
  3. 可靠性B:容錯,只要集羣中的大多數redis節點正常,客戶端應該都可以獲取和釋放鎖。

基於master-slave架構實現的分佈式鎖與存在的問題

使用如下命令獲取鎖,resource_name不存在的時候,設置key爲resource_name, 過期時間爲30s

SET resource_name value NX PX 30000

使用del resource_name釋放鎖

del resource_name

這種實現方式會存在一個問題,問題是由於redis複製是異步複製的

  1. Client A在master上獲取到鎖
  2. master在將這個key複製到slave前宕機了
  3. slave升級爲master,slave這臺機器上沒有key爲resource_name的數據
  4. Client B在新的master獲取key爲resource_name的鎖,違反了安全性的保證

使用單節點正確獲取分佈式鎖的實現

獲取鎖命令:

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

這個key的值設爲“my_random_value”。這個值必須在所有獲取鎖請求的客戶端裏保持唯一。 基本上這個隨機值就是用來保證能安全地釋放鎖,我們可以用下面這個Lua腳本來告訴Redis:刪除這個key當且僅當這個key存在而且值是我期望的那個值。

這個很重要,因爲這可以避免誤刪其他客戶端得到的鎖,舉個例子,一個客戶端拿到了鎖,被某個操作阻塞了很長時間,過了超時時間後自動釋放了這個鎖,然後這個客戶端之後又嘗試刪除這個其實已經被其他客戶端拿到的鎖。所以單純的用DEL指令有可能造成一個客戶端刪除了其他客戶端的鎖,用上面這個腳本可以保證每個客戶單都用一個隨機字符串’簽名’了,這樣每個鎖就只能被獲得鎖的客戶端刪除了。

key值的超時時間,也叫做”鎖有效時間”。這個是鎖的自動釋放時間,也是一個客戶端在其他客戶端能搶佔鎖之前可以執行任務的時間,這個時間從獲取鎖的時間點開始計算。 所以現在我們有很好的獲取和釋放鎖的方式,在一個非分佈式的、單點的、保證永不宕機的環境下這個方式沒有任何問題。接下來我們看看無法保證這些條件的分佈式環境下我們該怎麼做。

redis官網提供了Redlock算法的解決方案

       在分佈式版本的算法裏我們假設我們有N個Redis master節點,這些節點都是完全獨立的,我們不用任何複製或者其他隱含的分佈式協調算法。我們已經描述瞭如何在單節點環境下安全地獲取和釋放鎖。因此我們理所當然地應當用這個方法在每個單節點裏來獲取和釋放鎖。在我們的例子裏面我們把N設成5,這個數字是一個相對比較合理的數值,因此我們需要在不同的計算機或者虛擬機上運行5個master節點來保證他們大多數情況下都不會同時宕機。一個客戶端需要做如下操作來獲取鎖:

1.獲取當前時間(單位是毫秒)。

2.輪流用相同的key和隨機值在N個節點上請求鎖,在這一步裏,客戶端在每個master上請求鎖時,會有一個和總的鎖釋放時間相比小的多的超時時間。比如如果鎖自動釋放時間是10秒鐘,那每個節點鎖請求的超時時間可能是5-50毫秒的範圍,這個可以防止一個客戶端在某個宕掉的master節點上阻塞過長時間,如果一個master節點不可用了,我們應該儘快嘗試下一個master節點。

3.客戶端計算第二步中獲取鎖所花的時間,只有當客戶端在大多數master節點上成功獲取了鎖(在這裏是3個),而且總共消耗的時間不超過鎖釋放時間,這個鎖就認爲是獲取成功了。

4.如果鎖獲取成功了,那現在鎖自動釋放時間就是最初的鎖釋放時間減去之前獲取鎖所消耗的時間。

5.如果鎖獲取失敗了,不管是因爲獲取成功的鎖不超過一半(N/2+1)還是因爲總消耗時間超過了鎖釋放時間,客戶端都會到每個master節點上釋放鎖,即便是那些他認爲沒有獲取成功的鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章