redLock可靠的分布式鎖

2021-09-27 12:01:28 字數 3513 閱讀 9906

本文是對 martin kleppmann 的文章 how to do distributed locking 部分內容的翻譯和總結,上次寫 redlock 的原因就是看到了 martin 的這篇文章,寫得很好,特此翻譯和總結。感興趣的同學可以翻看原文,相信會收穫良多。

開篇作者認為現在 redis 逐漸被使用到資料管理領域,這個領域需要更強的資料一致性和耐久性,這使得他感到擔心,因為這不是 redis 最初設計的初衷(事實上這也是很多業界程式設計師的誤區,越來越把 redis 當成資料庫在使用),其中基於 redis 的分布式鎖就是令人擔心的其一。

martin 指出首先你要明確你為什麼使用分布式鎖,為了效能還是正確性?為了幫你區分這二者,在這把鎖 fail 了的時候你可以詢問自己以下問題:

上述二者都是需求鎖的正確場景,但是你必須清楚自己是因為什麼原因需要分布式鎖。

如果你只是為了效能,那沒必要用 redlock,它成本高且複雜,你只用乙個 redis 例項也夠了,最多加個從防止主掛了。當然,你使用單節點的 redis 那麼斷電或者一些情況下,你會丟失鎖,但是你的目的只是加速效能且斷電這種事情不會經常發生,這並不是什麼大問題。並且如果你使用了單節點 redis,那麼很顯然你這個應用需要的鎖粒度是很模糊粗糙的,也不會是什麼重要的服務。

那麼是否 redlock 對於要求正確性的場景就合適呢?martin 列舉了若干場景證明 redlock 這種演算法是不可靠的。

這節裡 martin 先將 redlock 放在了一邊而是僅討論總體上乙個分布式鎖是怎麼工作的。在分布式環境下,鎖比 mutex 這類複雜,因為涉及到不同節點、網路通訊並且他們隨時可能無徵兆的 fail 。

martin 假設了乙個場景,乙個 client 要修改乙個檔案,它先申請得到鎖,然後修改檔案寫回,放鎖。另乙個 client 再申請鎖 … **流程如下:

// this code is broken

function writedata

(filename, data)

tryfinally

}

可惜即使你的鎖服務非常完美,上述**還是可能跪,下面的流程圖會告訴你為什麼:

上述圖中,得到鎖的 client1 在持有鎖的期間 pause 了一段時間,例如 gc 停頓。鎖有過期時間(一般叫租約,為了防止某個 client 崩潰之後一直占有鎖),但是如果 gc 停頓太長超過了鎖租約時間,此時鎖已經被另乙個 client2 所得到,原先的 client1 還沒有感知到鎖過期,那麼奇怪的結果就會發生,曾經 hbase 就發生過這種 bug。即使你在 client1 寫回之前檢查一下鎖是否過期也無助於解決這個問題,因為 gc 可能在任何時候發生,即使是你非常不便的時候(在最後的檢查與寫操作期間)。

如果你認為自己的程式不會有長時間的 gc 停頓,還有其他原因會導致你的程序 pause。例如程序可能讀取尚未進入記憶體的資料,所以它得到乙個 page fault 並且等待 page 被載入進快取;還有可能你依賴於網路服務;或者其他程序占用 cpu;或者其他人意外發生 sigstop 等。

… … 這裡 martin 又增加了一節列舉各種程序 pause 的例子,為了證明上面的**是不安全的,無論你的鎖服務多完美。

使用 fencing (柵欄)使得鎖變安全

修復問題的方法也很簡單:你需要在每次寫操作時加入乙個 fencing token。這個場景下,fencing token 可以是乙個遞增的數字(lock service 可以做到),每次有 client 申請鎖就遞增一次

client1 申請鎖同時拿到 token33,然後它進入長時間的停頓鎖也過期了。client2 得到鎖和 token34 寫入資料,緊接著 client1 活過來之後嘗試寫入資料,自身 token33 比 34 小因此寫入操作被拒絕。注意這需要儲存層來檢查 token,但這並不難實現。如果你使用 zookeeper 作為 lock service 的話那麼你可以使用 zxid 作為遞增數字。

但是對於 redlock 你要知道,沒什麼生成 fencing token 的方式,並且怎麼修改 redlock 演算法使其能產生 fencing token 呢?好像並不那麼顯而易見。因為產生 token 需要單調遞增,除非在單節點 redis 上完成但是這又沒有高可靠性,你好像需要引進一致性協議來讓 redlock 產生可靠的 fencing token。

redlock 無法產生 fencing token 早該成為在需求正確性的場景下棄用它的理由,但還有一些值得討論的地方。

學術界有個說法,演算法對時間不做假設:因為程序可能pause一段時間、資料報可能因為網路延遲延後到達、時鐘可能根本就是錯的。而可靠的演算法依舊要在上述假設下做正確的事情。

對於 failure detector 來說,timeout 只能作為猜測某個節點 fail 的依據,因為網路延遲、本地時鐘不正確等其他原因的限制。考慮到 redis 使用 gettimeofday,而不是單調的時鐘,會受到系統時間的影響,可能會突然前進或者後退一段時間,這會導致乙個 key 更快或更慢地過期。

可見,redlock 依賴於許多時間假設,它假設所有 redis 節點都能對同乙個 key 在其過期前持有差不多的時間、跟過期時間相比網路延遲很小、跟過期時間相比程序 pause 很短。

這節 martin 舉了個因為時間問題,redlock 不可靠的例子。

client1 從 abc 三個節點處申請到鎖,de由於網路原因請求沒有到達

c節點的時鐘往前推了,導致 lock 過期

client2 在cde處獲得了鎖,ab由於網路原因請求未到達

此時 client1 和 client2 都獲得了鎖

接下來 martin 又列舉了程序 pause 時而不是時鐘不可靠時會發生的問題:

client1 從 abcde 處獲得了鎖

當獲得鎖的 response 還沒到達 client1 時 client1 進入 gc 停頓

停頓期間鎖已經過期了

client2 在 abcde 處獲得了鎖

client1 gc 完成收到了獲得鎖的 response,此時兩個 client 又拿到了同一把鎖

同時長時間的網路延遲也有可能導致同樣的問題。

這些例子說明了,僅有在你假設了乙個同步性系統模型的基礎上,redlock 才能正常工作,也就是系統能滿足以下屬性:

網路延時邊界,即假設資料報一定能在某個最大延時之內到達

程序停頓邊界,即程序停頓一定在某個最大時間之內

時鐘錯誤邊界,即不會從乙個壞的 ntp 伺服器處取得時間

martin 認為 redlock 實在不是乙個好的選擇,對於需求效能的分布式鎖應用它太重了且成本高;對於需求正確性的應用來說它不夠安全。因為它對高危的時鐘或者說其他上述列舉的情況進行了不可靠的假設,如果你的應用只需要高效能的分布式鎖不要求多高的正確性,那麼單節點 redis 夠了;如果你的應用想要保住正確性,那麼不建議 redlock,建議使用乙個合適的一致性協調系統,例如 zookeeper,且保證存在 fencing token。

Redlock分布式鎖

這篇文章主要是對 redis 官方 刊登的 distributed locks with redis 部分內容的總結和翻譯。安全特性 互斥訪問,即永遠只有乙個 client 能拿到鎖 避免死鎖 最終 client 都可能拿到鎖,不會出現死鎖的情況,即使原本鎖住某資源的 client crash 了或...

redis分布式鎖可靠嗎

寫在前面的話 2020年2月22日來杭,杭州天氣不錯,晴空萬里,氣溫回暖,疫情彷佛散去,而我開始了既定的跳槽,投簡歷,刷面試片刻未敢停留。一周下來也差不多面了10來家公司,反饋還行,但是並沒有想象中的那麼好,總體來看杭州網際網路既沒有那麼好,也沒有想象的那麼槽。所以小夥伴們適度焦慮就ok,重要的還是...

使用redis構建可靠分布式鎖

關於分布式鎖的概念,具體實現方式,直接參閱下面兩個帖子,這裡就不多介紹了。分布式鎖的多種實現方式 分布式鎖總結 對於分布式鎖的幾種實現方式的優劣,這裡再列舉下 1.資料庫實現方式 優點 易理解 缺點 運算元據庫消耗較大,效能較低。為了處理一些異常,會使得整個方案越來越複雜 2.快取實現方式 優點 效...