基於 Redis 的分佈式鎖到底安全嗎?

單機 Redis 實現的分佈式鎖

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

SET lock_key random_value NX PX 10000
// do sth
eval "if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end"
複製代碼

2,注意事項(對釋放鎖的控制,以及鎖超時的控制)random_value 要保證惟一,能夠用 trace_id 來保證!
3,存在的問題,單機Redis只是依賴單臺 Redis ,當依賴的 Redis 掛掉以後會形成比較大的問題!
4,那麼部署 Redis 的主從能夠保證嗎?主要緣由是 Redis 主節點與從節點之間的數據同步是異步的。redis

分佈式 Redis 實現的分佈式鎖

Redlock 算法

Redlock 算法是基於 N 個徹底獨立的 Redis 節點(一般狀況下 N 能夠設置成 5)。
1,獲取當前時間(毫秒數)。算法

2,按順序依次向 N 個 Redis 節點執行獲取鎖的操做。這個獲取操做跟基於單 Redis 節點的獲取鎖的過程相同。爲了保證在某個 Redis 節點不可用的時候算法可以繼續運行,這個獲取鎖的操做還有一個超時時間(time out),它要遠小於鎖的有效時間(幾十毫秒量級)。客戶端在向某個 Redis 節點獲取鎖失敗(好比該Redis節點不可用,或者該 Redis 節點上的鎖已經被其它客戶端持有)之後,應該當即嘗試下一個 Redis 節點。數據庫

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

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

5,若是最終獲取鎖失敗了(可能因爲獲取到鎖的 Redis 節點個數少於 N/2+1 ,或者整個獲取鎖的過程消耗的時間超過了鎖的最初有效時間),那麼客戶端應該當即向全部 Redis 節點發起釋放鎖的操做(這裏來保證全部的 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,獲取鎖成功。
dom

Redis 給出的解決方案:延遲重啓,既當 Redis 節點掛掉以後不要馬上重啓,而要等待一個鎖的過時時間以後再重啓。異步

2,若是獲取鎖消耗的時間過多以致於沒法完成後續的操做,如何釋放鎖? 我的認爲須要業務方本身拿捏一個業務操做的須要消耗的時長,

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

DDIA 做者對於 Redlock 的觀點

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

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

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

上圖展現的是:因爲GC停頓形成的共享資源被多個客戶端訪問的問題。緣由是:客戶端在 GC 停頓形成鎖的等待超時從而被釋放,然而客戶端並不知道,認爲本身仍是處於持有鎖的狀態。

上圖展現的是:經過使用 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的方案,或者支持事務的數據庫。

最後的討論

在瞭解了 Redlock 算法的邏輯及其可能存在的問題以後,咱們能夠針對本身的業務場景進行選擇~

參考

How to do distributed locking

Is Redlock safe?

Distributed locks with Redis

關注咱們

關注咱們
相關文章
相關標籤/搜索