分佈式鎖的實現 Redlock 算法安全嗎?

1,單機實現分佈式鎖的腳本(官方推薦實現)

<code>SET lock_key random_value NX PX 10000// do stheval "if redis.call("get",KEYS[1]) == ARGV[1] then          return redis.call("del",KEYS[1])      else          return 0      end"/<code>

2,注意事項(對釋放鎖的控制,以及鎖超時的控制)

random_value 要保證唯一,可以用 trace_id 來保證!

3,存在的問題

單機Redis只是依賴單臺 Redis ,當依賴的 Redis 掛掉之後會造成比較大的問題!

分佈式 Redis 實現的分佈式鎖

Redlock 算法

Redlock 算法是基於 N 個完全獨立的 Redis 節點(通常情況下 N 可以設置成 5)。

1,獲取當前時間(毫秒數)。

2,按順序依次向N個Redis節點執行獲取鎖的操作。這個獲取操作跟前面基於單Redis節點的獲取鎖的過程相同,包含隨機字符串 random_value,也包含過期時間(比如 PX 10000,即鎖的有效時間)。為了保證在某個Redis節點不可用的時候算法能夠繼續運行,這個獲取鎖的操作還有一個超時時間(time out),它要遠小於鎖的有效時間(幾十毫秒量級)。客戶端在向某個 Redis 節點獲取鎖失敗以後,應該立即嘗試下一個 Redis 節點。這裡的失敗,應該包含任何類型的失敗,比如該Redis節點不可用,或者該 Redis 節點上的鎖已經被其它客戶端持有(注:Redlock 原文中這裡只提到了Redis節點不可用的情況,但也應該包含其它的失敗情況)。

3,計算整個獲取鎖的過程總共消耗了多長時間,計算方法是用當前時間減去第1步記錄的時間。如果客戶端從大多數Redis節點(>= N/2+1)成功獲取到了鎖,並且獲取鎖總共消耗的時間沒有超過鎖的有效時間(lock validity time),那麼這時客戶端才認為最終獲取鎖成功;否則,認為最終獲取鎖失敗。

4,如果最終獲取鎖成功了,那麼這個鎖的有效時間應該重新計算,它等於最初的鎖的有效時間減去第3步計算出來的獲取鎖消耗的時間。

5,如果最終獲取鎖失敗了(可能由於獲取到鎖的 Redis 節點個數少於 N/2+1 ,或者整個獲取鎖的過程消耗的時間超過了鎖的最初有效時間),那麼客戶端應該立即向所有 Redis 節點發起釋放鎖的操作。

Redlock 算法實現的前提是基於不同的機器具有相同的時鐘,或者誤差很小可以忽略不計的假設。(這也是DDIA作者噴的一點)

存在的問題:

1,關於Redis的持久化的問題

假設一共有5個Redis節點:A, B, C, D, E。設想發生瞭如下的事件序列:

1.1 客戶端1成功鎖住了A, B, C,獲取鎖成功(但D和E沒有鎖住)。

1.2 節點C崩潰重啟了,但客戶端1在C上加的鎖沒有持久化下來,丟失了。

1.3 節點C重啟後,客戶端2鎖住了C, D, E,獲取鎖成功。

Redis 給出的解決方案:延遲重啟,既當 Redis 節點掛掉之後不要立刻重啟,而要等待一個鎖的過期時間之後再重啟。

2,如果獲取鎖消耗的時間過多以至於無法完成後續的操作,如何釋放鎖?

個人認為需要業務方自己拿捏一個業務操作的需要消耗的時長,

3,antirez在設計Redlock的時候,是充分考慮了網絡延遲和程序停頓所帶來的影響的。但是,對於客戶端和資源服務器之間的延遲(即發生在算法第3步之後的延遲),antirez 是承認所有的分佈式鎖的實現,包括 Redlock,是沒有什麼好辦法來應對的。

DDIA 作者的 blog(How to do distributed locking)

在 Martin 的這篇文章中,他把鎖的用途分為兩種:

  • 為了效率(efficiency),協調各個客戶端避免做重複的工作。即使鎖偶爾失效了,只是可能把某些操作多做一遍而已,不會產生其它的不良後果。比如重複發送了一封同樣的 email。
  • 為了正確性(correctness)。在任何情況下都不允許鎖失效的情況發生,因為一旦發生,就可能意味著數據不一致(inconsistency),數據丟失,文件損壞,或者其它嚴重的問題。

1,帶有自動過期功能的分佈式鎖,必須提供某種fencing機制來保證對共享資源的真正的互斥保護。Redlock 提供不了這樣一種機制。

分佈式鎖的實現 Redlock 算法安全嗎?

上圖展示的是:由於GC停頓造成的共享資源被多個客戶端訪問的問題。原因是:客戶端在 GC 停頓造成鎖的等待超時從而被釋放,然而客戶端並不知道,認為自己還是處於持有鎖的狀態。

分佈式鎖的實現 Redlock 算法安全嗎?

上圖展示的是:通過使用 fencing tokens 來解決鎖失效未釋放的問題。Redis 指出上圖的漏洞,如果客戶端1和客戶端2都發生了GC pause,兩個fencing token都延遲了,它們幾乎同時到達了資源服務器,但保持了順序,那麼資源服務器是不是就檢查不出問題了?這時對於資源的訪問是不是就發生衝突了?

2,Redlock 構建在一個不夠安全的系統模型之上。它對於系統的記時假設(timing assumption)有比較強的要求,而這些要求在現實的系統中是無法保證的。

Redlock 算法對機器時鐘的強依賴,Martin 認為算法的實現不應該對時序做任何假設:進程可能會暫停任意時長,數據包可能會在網絡中被任意延遲,時鐘可能會被任意錯誤,即便如此,該算法仍可以正確執行並給出正確結果。

關於時鐘的不可靠性:Redis 作者認為 Redlock 對時鐘的要求,並不需要完全精確,它只需要時鐘差不多精確就可以了。

3,在 Redlock 第三步完成之後的網絡延遲,也為造成 Redlock 算法的失效,但是這個問題並不是 Redlock 算法獨有的

Martin得出瞭如下的結論:

  • 如果是為了效率(efficiency)而使用分佈式鎖,允許鎖的偶爾失效,那麼使用單 Redis 節點的鎖方案就足夠了,簡單而且效率高。Redlock 則是個過重的實現(heavy weight)。
  • 如果是為了正確性(correctness)在很嚴肅的場合使用分佈式鎖,那麼不要使用 Redlock。它不是建立在異步模型上的一個足夠強的算法,它對於系統模型的假設中包含很多危險的成分(對於 timing)。而且,它沒有一個機制能夠提供 fencing token。那應該使用什麼技術呢?Martin認為,應該考慮類似Zookeeper的方案,或者支持事務的數據庫。


分享到:


相關文章: