基於Redis的分散式鎖到底安全嗎?
- 2020 年 2 月 18 日
- 筆記
單機 Redis 實現的分散式鎖
1,單機實現分散式鎖的腳本(官方推薦實現)
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 實現的分散式鎖
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步計算出來的獲取鎖消耗的時間。
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,獲取鎖成功。
Redis 給出的解決方案:延遲重啟,既當 Redis 節點掛掉之後不要立刻重啟,而要等待一個鎖的過期時間之後再重啟。
2,如果獲取鎖消耗的時間過多以至於無法完成後續的操作,如何釋放鎖? 個人認為需要業務方自己拿捏一個業務操作的需要消耗的時長,
3,antirez在設計Redlock的時候,是充分考慮了網路延遲和程式停頓所帶來的影響的。但是,對於客戶端和資源伺服器之間的延遲(即發生在演算法第3步之後的延遲),antirez 是承認所有的分散式鎖的實現,包括 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 演算法的邏輯及其可能存在的問題之後,我們可以針對自己的業務場景進行選擇~
參考
作 者:haifeiWu 原文鏈接:https://www.hchstudio.cn/article/2020/7bc5/ 版權聲明:非特殊聲明均為本站原創作品,轉載時請註明作者和原文鏈接。