【萬字長文之求錘得錘的故事】Redis鎖從面試連環炮聊到神仙打架。

why技術發表於2020-03-15

本次寫的是一個Redis作者求錘得錘的故事,描述了他與另外一位分散式系統的大神之間的battle。這場battle難分伯仲,沒有最後的贏家。如果一定要選出誰是最大的贏家的話,那一定是吃瓜網友。讓吃瓜網友深刻的體會到:看起來那麼無懈可擊的想法,細細推敲之下,並不是那麼天衣無縫。

又到了一週一次的分享時間啦,老規矩,還是先荒腔走板的聊聊生活。

有上面的圖是讀大學的時候,一次自行車騎行途中隊友抓拍的我的照片。拍照的地方,名字叫做牛背山,一個名字很 low,實際很美的地方。

那條上山的路很難騎,超級爛路和極度變態的陡坡。真是一種折磨,是對意志力的完全考驗。

在我們幾近崩潰,彈盡糧絕,離山頂還有近兩個多小時的時候,一個卡車司機主動要求把我們免費帶到山頂。我們拒絕了。

因為同行的星哥他說了一句話:“騎行牛背山這件事情,我這輩子只會做這一次。現在的情況還不是那麼糟,如果我搭車上去了,之後想起來我會有遺憾的。所以我更情願推車。”

第二天下午山上下了一場暴風雪。於是我們幾個南方孩子在這少見的雪景中肆意打鬧。打雪仗,堆雪人,滑雪......

晚上雪停了之後,我看到了讓我終身難忘的場景。星空,美麗到讓人想哭的星空!

後來,在人生路上的很多場景中,我都會想起星哥的那句話:這件事,我這輩子只會做這一次,我不想留下遺憾。還會想起那晚璀璨的、觸手可及般的星空。

堅持不住的時候再堅持一下,確實是一種難能可貴的精神。

好了,說迴文章。

背景鋪墊

面試的時候,不管你的簡歷寫沒寫 Redis,它基本上是一個繞不過的話題。

為了引出本文要討論的關於 Redlock 的神仙打架的問題,我們就得先通過一個面試連環炮:

1.Redis 做分散式鎖的時候有需要注意的問題?

2.如果是 Redis 是單點部署的,會帶來什麼問題?

3.那你準備怎麼解決單點問題呢?

4.叢集模式下,比如主從模式,有沒有什麼問題呢?

5.你知道 Redis 是怎麼解決叢集模式也不靠譜的問題的嗎?

6.那你簡單的介紹一下 Redlock 吧?

7.你覺得 Redlock 有什麼問題呢?

很明顯,上面是一個常規的面試連環套路題。中間還可以插入很多其他的 Redis 的考察點,我這裡就不做擴充套件了。

單點的 Redis 做分散式鎖不靠譜,導致了基於 Redis 叢集模式的分散式鎖解決方案的出現。

基於 Redis 叢集模式的分散式鎖解決方案還是不靠譜,Redis 的作者提出了 Redlock 的解決方案。

Redis 作者提出的 Redlock 的解決方案,另一位分散式系統的大神覺得它不靠譜,於是他們之間開始了 battle。

基於這場 battle,又引發了更多的討論。

這場 battle 難分伯仲,沒有最後的贏家。如果一定要選出誰是最大的贏家的話,那一定是吃瓜網友。因為對於吃瓜網友來說(比如我),可以從兩位大神的交鋒中學習到很多東西。

讓你深刻的體會到:看起來那麼無懈可擊的想法,細細推敲之下,並不是那麼天衣無縫。

所以本文就按照下面的五個模組展開講述。

先來一波勸退:本文近1.2w字,謹慎觀看。看不下去不要緊,點個贊就是對於我最大的鼓勵。奧利給!

單點Redis

按照我的經驗,當面試聊到 Redis 的時候,百分之 90 的朋友都會說**:Redis在我們的專案中是用來做熱點資料快取的**。

然後百分之百的面試官都會問:

Redis除了拿來做快取,你還見過基於Redis的什麼用法?

接下來百分之 80 的朋友都會說到:我們還用 Redis 做過分散式鎖。

(當然, Redis 除了快取、分散式鎖之外還有非常非常多的奇技淫巧,不是本文重點,大家有興趣的可以自己去了解一下。)

那麼面試官就會接著說:

那你給我描述(或者寫一下虛擬碼)基於Redis的加鎖和釋放鎖的細節吧。

注意面試官這裡說的是加鎖和釋放鎖的細節,魔鬼都在細節裡。

問這個問題面試官無非是想要聽到下面幾個關鍵點:

關鍵點一:原子命令加鎖。因為有的“年久失修”的文章中對於 Redis 的加鎖操作是先set key,再設定 key 的過期時間。這樣寫的根本原因是在早期的 Redis 版本中並不支援原子命令加鎖的操作。不是原子操作會帶來什麼問題,就不用我說了吧?如果你不知道,你先回去等通知吧。

而在 2.6.12 版本後,可以通過向 Redis 傳送下面的命令,實現原子性的加鎖操作:

SET key random_value NX PX 30000

關鍵點二:設定值的時候,放的是random_value。而不是你隨便扔個“OK”進去。

先解釋一下上面的命令中的幾個引數的含義:

random_value:是由客戶端生成的一個隨機字串,它要保證在足夠長的一段時間內在所有客戶端的所有獲取鎖的請求中都是唯一的。

NX:表示只有當要設定的 key 值不存在的時候才能 set 成功。這保證了只有第一個請求的客戶端才能獲得鎖,而其它客戶端在鎖被釋放之前都無法獲得鎖。

PX 30000:表示這個鎖有一個 30 秒的自動過期時間。當然,這裡 30 秒只是一個例子,客戶端可以選擇合適的過期時間。

再解釋一下為什麼 value 需要設定為一個隨機字串。這也是第三個關鍵點。

關鍵點三:value 的值設定為隨機數主要是為了更安全的釋放鎖,釋放鎖的時候需要檢查 key 是否存在,且 key 對應的值是否和我指定的值一樣,是一樣的才能釋放鎖。所以可以看到這裡有獲取、判斷、刪除三個操作,為了保障原子性,我們需要用 lua 指令碼。

(基本上能答到這幾個關鍵點,面試官也就會進入下一個問題了。常規熱身送分題呀,朋友們,得記住了。)

叢集模式

面試官就會接著問了:

經過剛剛的討論,我們已經有較好的方法獲取鎖和釋放鎖。基於Redis單例項,假設這個單例項總是可用,這種方法已經足夠安全。如果這個Redis節點掛掉了呢?

到這個問題其實可以直接聊到 Redlock 了。但是你別慌啊,為了展示你豐富的知識儲備(瘋狂的刷題準備),你得先自己聊一聊 Redis 的叢集,你可以這樣去說:

為了避免節點掛掉導致的問題,我們可以採用Redis叢集的方法來實現Redis的高可用。

Redis叢集方式共有三種:主從模式,哨兵模式,cluster(叢集)模式

其中主從模式會保證資料在從節點還有一份,但是主節點掛了之後,需要手動把從節點切換為主節點。它非常簡單,但是在實際的生產環境中是很少使用的。

哨兵模式就是主從模式的升級版,該模式下會對響應異常的主節點進行主觀下線或者客觀下線的操作,並進行主從切換。它可以保證高可用。

cluster (叢集)模式保證的是高併發,整個叢集分擔所有資料,不同的 key 會放到不同的 Redis 中。每個 Redis 對應一部分的槽。

(上面三種模式也是面試重點,可以說很多道道出來,由於不是本文重點就不詳細描述了。主要表達的意思是你得在面試的時候遇到相關問題,需要展示自己是知道這些東西的,都是面試的套路。)

在上面描述的叢集模式下還是會出現一個問題,由於節點之間是採用非同步通訊的方式。如果剛剛在 Master 節點上加了鎖,但是資料還沒被同步到 Salve。這時 Master 節點掛了,它上面的鎖就沒了,等新的 Master 出來後(主從模式的手動切換或者哨兵模式的一次 failover 的過程),就可以再次獲取同樣的鎖,出現一把鎖被拿到了兩次的場景。

鎖都被拿了兩次了,也就不滿足安全性了。一個安全的鎖,不管是不是分散式的,在任意一個時刻,都只有一個客戶端持有。

Redlock簡介

為了解決上面的問題,Redis 的作者提出了名為 Redlock 的演算法。

在 Redis 的分散式環境中,我們假設有 N 個 Redis Master。這些節點完全互相獨立,不存在主從複製或者其他叢集協調機制。

前面已經描述了在單點 Redis 下,怎麼安全地獲取和釋放鎖,我們確保將在 N 個例項上使用此方法獲取和釋放鎖。

在下面的示例中,我們假設有 5 個完全獨立的 Redis Master 節點,他們分別執行在 5 臺伺服器中,可以保證他們不會同時當機。

從官網上我們可以知道,一個客戶端如果要獲得鎖,必須經過下面的五個步驟:

步驟描述來源:http://redis.cn/topics/distlock.html

1.獲取當前 Unix 時間,以毫秒為單位。

2.依次嘗試從 N 個例項,使用相同的 key 和隨機值獲取鎖。在步驟 2,當向 Redis 設定鎖時,客戶端應該設定一個網路連線和響應超時時間,這個超時時間應該小於鎖的失效時間。例如你的鎖自動失效時間為 10 秒,則超時時間應該在 5-50 毫秒之間。這樣可以避免伺服器端 Redis 已經掛掉的情況下,客戶端還在死死地等待響應結果。如果伺服器端沒有在規定時間內響應,客戶端應該儘快嘗試另外一個 Redis 例項。

3.客戶端使用當前時間減去開始獲取鎖時間(步驟 1 記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數(這裡是 3 個節點)的 Redis 節點都取到鎖,並且使用的時間小於鎖失效時間時,鎖才算獲取成功。

4.如果取到了鎖,key 的真正有效時間等於有效時間減去獲取鎖所使用的時間(步驟 3 計算的結果)。

5.如果因為某些原因,獲取鎖失敗(沒有在至少 N/2+1 個Redis例項取到鎖或者取鎖時間已經超過了有效時間),客戶端應該在所有的 Redis 例項上進行解鎖(即便某些 Redis 例項根本就沒有加鎖成功)。

通過上面的步驟我們可以知道,只要大多數的節點可以正常工作,就可以保證 Redlock 的正常工作。這樣就可以解決前面單點 Redis 的情況下我們討論的節點掛掉,由於非同步通訊,導致鎖失效的問題。

但是,還是不能解決故障重啟後帶來的鎖的安全性的問題。你想一下下面這個場景:

我們一共有 A、B、C 這三個節點。

1.客戶端 1 在 A,B 上加鎖成功。C 上加鎖失敗。

2.這時節點 B 崩潰重啟了,但是由於持久化策略導致客戶端 1 在 B 上的鎖沒有持久化下來。 客戶端 2 發起申請同一把鎖的操作,在 B,C 上加鎖成功。

3.這個時候就又出現同一把鎖,同時被客戶端 1 和客戶端 2 所持有了。

(接下來又得說一說Redis的持久化策略了,全是知識點啊,朋友們)

比如,Redis 的 AOF 持久化方式預設情況下是每秒寫一次磁碟,即 fsync 操作,因此最壞的情況下可能丟失 1 秒的資料。

當然,你也可以設定成每次修改資料都進行 fsync 操作(fsync=always),但這會嚴重降低 Redis 的效能,違反了它的設計理念。(我也沒見過這樣用的,可能還是見的太少了吧。)

而且,你以為執行了 fsync 就不會丟失資料了?天真,真實的系統環境是複雜的,這都已經脫離 Redis 的範疇了。上升到伺服器、系統問題了。

所以,根據墨菲定律,上面舉的例子:由於節點重啟引發的鎖失效問題,總是有可能出現的。

為了解決這一問題,Redis 的作者又提出了延遲重啟(delayed restarts)的概念

意思就是說,一個節點崩潰後,不要立即重啟它,而是等待一定的時間後再重啟。等待的時間應該大於鎖的過期時間(TTL)。這樣做的目的是保證這個節點在重啟前所參與的鎖都過期。相當於把以前的帳勾銷之後才能參與後面的加鎖操作。

但是有個問題就是:在等待的時間內,這個節點是不對外工作的。那麼如果大多數節點都掛了,進入了等待。就會導致系統的不可用,因為系統在TTL時間內任何鎖都將無法加鎖成功。

Redlock 演算法還有一個需要注意的點是它的釋放鎖操作。

釋放鎖的時候是要向所有節點發起釋放鎖的操作的。這樣做的目的是為了解決有可能在加鎖階段,這個節點收到加鎖請求了,也set成功了,但是由於返回給客戶端的響應包丟了,導致客戶端以為沒有加鎖成功。所以,釋放鎖的時候要向所有節點發起釋放鎖的操作。

你可能覺得這不是常規操作嗎?

有的細節就是這樣,說出來後覺得不過如此,但是有可能自己就是想不到這個點,導致問題的出現,所以我們才會說:細節,魔鬼都在細節裡。

好了,簡介大概就說到這裡,有興趣的朋友可以再去看看官網,補充一下。

中文:http://redis.cn/topics/distlock.html
英文:https://redis.io/topics/distlock

好了,經過這麼長,這麼長的鋪墊,我們終於可以進入到神仙打架環節。

神仙打架

神仙一:Redis 的作者 antirez 。有的朋友對英文名字不太敏感,所以後面我就叫他捲髮哥吧。

神仙二:分散式領域專家 Martin Kleppmann,我們叫他長髮哥吧。

看完上面兩位神仙的照片,再看看我為了寫這篇文章又日漸稀少的頭髮,我忍不住哭出聲來。可能只有給我點贊,才能平復我的心情吧。

捲髮哥在官網介紹 Redlock 頁面的最後寫到:如果你也是使用分散式系統的人員,你的觀點和意見非常重要,歡迎和我們討論。

於是,“求錘得錘”!這一錘,錘出了眾多的吃瓜網友,其中不乏在相關領域的專業人士。

長髮哥出錘

故事得從 2016年2月8號 長髮哥釋出的一篇文章《How to do distributed locking》說起:

文章地址:http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

這一部分直接翻譯過來就是:

作為本書(《資料密集型應用系統設計》)研究的一部分,我在Redis網站上 看到了一種稱為Redlock的演算法。該演算法聲稱在Redis實現容錯的分散式鎖(或更確切地說, 租約),並且該頁面要求來自分散式系統人員的反饋。這個演算法讓我產生了一些思考,因此我花了一些時間寫了我的這篇文章。

由於Redlock已經有10多個獨立的實現,而且我們不知道誰已經在依賴此演算法,因此我認為值得公開分享我的筆記。我不會討論Redis的其他方面,其中一些已經在其他地方受到了批評 。

你看這個文章,開頭就是火藥味十足:你說要反饋,那我就給你反饋。而且你這個東西有其他問題,我也就不說了。(其實作者在這篇文章中也說了,他很喜歡並且也在使用 Redis,只是他覺得這個 Redlock 演算法是不嚴謹的)

長髮哥主要圍繞了下面的這張圖進行了展開:

要是一眼沒看明白,我再給你一箇中文版的,來自長髮哥於2017年出版的書《資料密集型應用系統設計》:

可以看到上面的圖片中提到了申請租約、租約到期的關鍵詞,租約其實就是可以理解為帶超時時間的鎖。

而在書中,這張圖片的下面寫的描述這樣的,你咂摸咂摸:

拿 HBase 舉例,其設計的目標是確儲存儲系統的檔案一次只能由一個客戶端訪問,如果多個客戶端試圖同時寫入該檔案,檔案就會被破壞。那麼上面的圖片解釋起來就是:

1.客戶端 1 先去申請鎖,並且成功獲取到鎖。之後客戶端進行了長時間的 GC 導致了 STW 的情況。

2.在 STW 期間,客戶端 1 獲取的鎖的超時時間到了,鎖也就失效了。

3.由於客戶端 1 的鎖已經過期失效了,所以客戶端 2 去申請鎖就可以成功獲得鎖。

4.客戶端 2 開始寫檔案,並完成檔案的寫入。

5.客戶端 1 從 STW 中恢復過來,他並不知道自己的鎖過期了,還是會繼續執行檔案寫入操作,導致客戶端 2 寫入的檔案被破壞。而且可以看到,它沒有滿足鎖在任意時刻只有一個客戶端持有的原則,即沒有滿足互斥性。

書裡面沒有明說,但是你品一品,這裡的鎖服務難道不是在說 Redis?

有的朋友就會說了,那客戶端 1 寫入檔案的時候,再判斷一下自己的鎖有沒有過期不就可以了嗎?

你可真是個小機靈鬼呢,那我問你,GC 可能是發生在任何時間的,萬一 GC 發生在判斷之後呢?

你繼續懟我,如果客戶端使用的是沒有 GC 的語言呢?

**GC 不是導致執行緒暫停的唯一原因啊,朋友們。**發生這種情況的原因有很多的,你看看長髮哥書裡舉的例子:

上面的內容總結起來,就是就算鎖服務是正常的,但是由於鎖是有持有時間的,由於客戶端阻塞、長時間的 GC 或者網路原因,導致共享資源被一個以上的客戶端同時訪問了。

其實上面長髮哥在書裡直接說了:這是不正確的實現。

你多品一品,上面的圖是不是有點像由於 Redis 鎖的過期時間設定的不合理,導致前一個任務還沒執行完成,但是鎖的時間到期了,後一個任務也申請到了鎖。

對於這種場景,Redission 其實有自己的看門狗機制。但是不在這次 Redlock 的討論範圍內,所以這裡就不描述了。

長髮哥提出的解決方案是什麼呢?

他稱為:fencing token。

長髮哥認為使用鎖和租約機制來保護資源的併發訪問時,必須確保因為異常原因,導致鎖過期的那個節點不能影響其他正常的部分,要實現這一目標,可以採用一直相當簡單的 fencing(柵欄)。

假設每次鎖服務在授予鎖或者租約時,還會同時返回一個 fencing 令牌,該令牌每次授予都會遞增。

然後,要求客戶端每次向儲存系統傳送寫請求時,都必須包含所持有的 fencing 令牌。儲存系統需要對令牌進行校驗,發現如果已經處理過更高令牌的請求,則拒絕執行該請求。

比如下面的圖片:

1.客戶端 1 獲得一個具有超時時間的鎖的同時得到了令牌號 33,但隨後陷入了一個長時間的暫停直到鎖到期。

2.這時客戶端2已經獲得了鎖和令牌號 34 ,然後傳送寫請求(以及令牌號 34 )到儲存服務。

3.接下來客戶端 1 恢復過來,並以令牌號 33 來嘗試寫入,儲存伺服器由於記錄了最近已經完成了更高令牌號(34 ),因此拒絕令牌號 33 的寫請求。

這種版本號的機制,讓我不禁想起了 Zookeeper。當使用 ZK 做鎖服務時,可以用事務標識 zxid 或節點版本 cversion 來充當 fencing 令牌,這兩個都可以滿足單調遞增的要求。

在長髮哥的這種機制中,實際上就是要求資源本身必須主動檢查請求所持令牌資訊,如果發現已經處理過更高令牌的請求,要拒絕持有低令牌的所有寫請求。

但是,不是所有的資源都是資料庫裡面的資料,我們可以通過版本號去支援額外的令牌檢查的,那麼對於不支援額外的令牌檢查資源,我們也可以藉助這種思想繞過這個限制,比如對於訪問檔案儲存服務的情況,我們可以將令牌嵌入到檔名中。

總之,為了避免在鎖保護之外發生請求處理,需要進行額外的檢查機制。

長髮哥在書中也說到了:在服務端檢查令牌可能看起來有點複雜,但是這其實是推薦的正確的做法:系統服務不能假定所有的客戶端都表現的符合預期。從安全形度講,服務端必須防範這種來自客戶端的濫用。

這個就類似於我們作為後端開發人員,也不能相信來自前端或者其他介面過來的資料,必須對其進行校驗。

到這裡長髮哥鋪墊完成了,開始轉頭指向 RedLock,他認為 Redlock 是一個嚴重依賴系統時鐘的分散式鎖。

他舉了一個例子:

1.客戶端 1 從 Redis 節點 A, B, C 成功獲取了鎖。由於網路問題,無法訪問 D 和 E。

2.節點 C 上的時鐘發生了向前跳躍,導致它上面維護的鎖過期了。

3.客戶端 2 從 Redis 節點 C, D, E 成功獲取了同一個資源的鎖。由於網路問題,無法訪問 A 和 B。 現在,客戶端 1 和客戶端 2 都認為自己持有了鎖。

這樣的場景是可能出現的,因為 Redlock 嚴重依賴系統時鐘,所以一旦系統的時間變得不準確了,那麼該演算法的安全性也就得不到保障了。

長髮哥舉這個例子其實是為了輔佐他前面提出的觀點:一個好的分散式演算法應該是基於非同步模型的,演算法的安全性不應該依賴與任何記時假設,就是不能把時間作為安全保障的。在非同步模型中,程式暫停、訊息在網路中延遲甚至丟失、系統時間錯誤這些因素都不應該影響它的安全性,只能影響到它的活性。

用大白話說,就是在極其極端的情況下,分散式系統頂天了也就是在有限的時間內不能給出結果而已,而不能給出一個錯誤的結果。

這樣的演算法實際上是存在的,比如 Paxos、Raft。很明顯,按照這個標準, Redlock 的安全級別是不夠的。

而對於捲髮哥提出的延遲啟動方案,長髮哥還是一棒子打死:你延遲啟動咋的?延遲啟動還不是依賴於合理準確的時間度量。

可能是長髮哥覺得舉這個時鐘跳躍的例子不夠好的,大家都可能認為時鐘跳躍是不現實的,因為對正確配置NTP就能擺正時鐘非常有信心。

在這種情況下,他舉了一個程式暫停可能導致演算法失敗的示例:

1.客戶端 1 向 Redis 節點 A, B, C, D, E 發起鎖請求。

2.各個 Redis 節點已經把請求結果返回給了客戶端 1,但客戶端 1 在收到請求結果之前進入了長時間的 GC 階段。

3.長時間的 GC,導致在所有的 Redis 節點上,鎖過期了。

4.客戶端 2 在 A, B, C, D, E 上申請並獲取到了鎖。

5.客戶端 1 從 GC 階段中恢復,收到了前面第 2 步來自各個 Redis 節點的請求結果。客戶端 1 認為自己成功獲取到了鎖。

6.客戶端 1 和客戶端 2 現在都認為自己持有了鎖。

其實只要十分清楚 Redlock 的加鎖過程,我們就知道,這種情況其實對於 Redlock 是沒有影響的,因為在第 5 步,客戶端 1 從 GC 階段中恢復過來以後,在 Redlock 演算法中,(我們前面 Redlock 簡介的時候提到的第四步)如果取到了鎖,key 的真正有效時間等於有效時間減去獲取鎖所使用的時間。

所以客戶端1通過這個檢查發現鎖已經過期了,不會再認為自己成功獲取到鎖了。

而隨後捲髮哥的回擊中也提到了這點。

但是,細細想來,我覺得長髮哥的意圖不在於此。拋開上面的問題來講,他更想突出的是,一個鎖在客戶端拿到後,還沒使用就過期了,這是不好的。從客戶端的角度來看,就是這玩意不靠譜啊,你給我一把鎖,我還沒用呢,你就過期了?

除了上面說的這些點外,長髮哥還提出了一個算是自己的經驗之談吧:

我們獲取鎖的用途是什麼?

在他看來不外乎兩個方面,效率和正確性。他分別描述如下:

如果是為了效率,那麼就是要協調各個客戶端,避免他們做重複的工作。這種場景下,即使鎖偶爾失效了,只是可能出現兩個客戶端完成了同樣的工作,其結果是成本略有增加(您最終向 AWS 支付的費用比原本多5美分),或者帶來不便(例如,使用者最終兩次收到相同的電子郵件通知)。

如果是為了正確性,那麼在任何情況下都不允許鎖失效的情況發生,因為一旦發生,就可能意味著資料不一致,資料丟失,檔案損壞,或者其它嚴重的問題。(比如個患者注射了兩倍的藥劑)

最後,長髮哥得出的結論是:neither fish nor fowl(不倫不類)

對於提升效率的場景下,使用分散式鎖,允許鎖的偶爾失效,那麼使用單 Redis 節點的鎖方案就足夠了,簡單而且效率高。用 Redlock 太重。

對於正確性要求高的場景下,它是依賴於時間的,不是一個足夠強的演算法。Redlock並沒有保住正確性。

那應該使用什麼技術呢?

長髮哥認為,應該考慮類似 Zookeeper 的方案,或者支援事務的資料庫。

捲髮哥回擊

長髮哥發出《How to do distributed locking》這篇文章的第二天,捲髮哥就進行了回擊,釋出了名為《Is Redlock safe?》的文章。

文章地址:http://antirez.com/news/101

要說大佬不愧是大佬,捲髮哥的回擊條理清楚,行文流暢。他總結後認為長髮哥覺得 Redlock 不安全主要分為兩個方面:

1.帶有自動過期功能的分散式鎖,需要一種方法(fencing機制)來避免客戶端在過期時間後使用鎖時出現問題,從而對共享資源進行真正的互斥保護。長髮哥說Redlock沒有這種機制。

2.長髮哥說,無論問題“1”如何解決,該演算法本質上都是不安全的,因為它對系統模型進行了記時假設,而這些假設在實際系統中是無法保證的。

對於第一個點,捲髮哥列了5大點來反駁這個問題,其中一個重要的觀點是他認為雖然 Redlock 沒有提供類似於fencing機制那樣的單調遞增的令牌,但是也有一個隨機串,把這個隨機串當做token,也可以達到同樣的效果啊。當需要和共享資源互動的時候,我們檢查一下這個token是否發生了變化,如果沒有再執行“獲取-修改-寫回”的操作。

最終得出的結論是一個靈魂反問:既然在鎖失效的情況下已經存在一種fencing機制能繼續保持資源的互斥訪問了,那為什麼還要使用一個分散式鎖並且還要求它提供那麼強的安全性保證呢?

然而第二個問題,對於網路延遲或者 GC 暫停,我們前面分析過,對 Redlock 的安全性並不會產生影響,說明捲髮哥在設計的時候其實是考慮過時間因素帶來的問題的。

但是如果是長髮哥提出的時鐘發生跳躍,很明顯,捲髮哥知道如果時鐘發生跳躍, Redlock 的安全性就得不到保障,這是他的命門。

但是對於長髮哥寫時鐘跳躍的時候提出的兩個例子:

1.運維人員手動修改了系統時鐘。

2.從NTP服務收到了一個大的時鐘更新事件。

捲髮哥進行了回擊:

第一點這個運維人員手動修改時鐘,屬於人為因素,這個我也沒辦法啊,人家就是要搞你,怎麼辦?加強管理,不要這樣做。

第二點從NTP服務收到一個大的時鐘更新,對於這個問題,需要通過運維來保證。需要將大的時間更新到伺服器的時候,應當採取少量多次的方式。多次修改,每次更新時間儘量小。

關於這個地方的爭論,就看你是信長髮哥的時間一定會跳躍,還是信捲髮哥的時間跳躍我們也是可以處理的。

關於時鐘跳躍,有一篇文章可以看看,也是這次神仙打架導致的產物:

https://jvns.ca/blog/2016/02/09/til-clock-skew-exists/

文章得出的最終結論是:時鐘跳躍是存在的。

其實我們大家應該都經歷過時鐘跳躍的情況,你還記得2016年的最後一天,當時有個“閏秒”的概念嗎?導致2017年1月1日出現了07:59:60的奇觀。

打架的焦點

經過這樣的一來一回,其實雙方打架的焦點就很明確了,就是大延遲對分散式鎖帶來的影響。

而對於大延遲給Redlock帶來的影響,就是長髮哥分析的那樣,鎖到期了,業務還沒執行完。捲髮哥認為這種影響不單單針對 Redlock ,其他具有自動釋放鎖的分散式鎖也是存在一樣的問題。

而關於大延遲的問題,我在某社交平臺上找到了兩位神仙的下面的對話:

捲髮哥問:我想知道,在我發文回覆之後,我們能否在一點上達成一致,就是大的訊息延遲不會給Redlock的執行造成損害。

長髮哥答:對於客戶端和鎖伺服器之間的訊息延遲,我同意你的觀點。但客戶端和被訪問資源之間的延遲還是有問題的。

所以通過捲髮哥的回擊文章和某社交平臺的記錄,他是同意大的系統時鐘跳躍會造成 Redlock 失效的。在這一點上,他與長髮哥的觀點的不同在於,他認為在實際系統中是可以通過好的運維方式避免大的時鐘跳躍的。

所以到這裡,兩位神仙好像又達到了一個平衡,實現了爭論上的求同存異。

打架總結

作為一個網際網路行業的從業者,也是分散式系統的使用者,讀完他們的文章以及由此文章衍生出來的知識點後,受益良多,於是寫下此文作為學習總結,也與大家分享。本文還有很多不足之處,還請各位海涵。

如同文章開篇說的,這場爭論沒有最後的贏家。很明顯捲髮哥是沒有說服長髮哥的,因為在長髮哥2017年出版的《資料密集型應用系統設計》一書中,專門有一小節的名稱叫做:不可靠的時鐘

其實在這場爭論的最後,長髮哥對這場爭論進行了一個非常感性的總結,他說:

下面翻譯來自:https://www.jianshu.com/p/dd66bdd18a56

對我來說最重要的一點在於:我並不在乎在這場辯論中誰對誰錯 —— 我只關心從其他人的工作中學到的東西,以便我們能夠避免重蹈覆轍,並讓未來更加美好。前人已經為我們創造出了許多偉大的成果:站在巨人的肩膀上,我們得以構建更棒的軟體。

對於任何想法,務必要詳加檢驗,通過論證以及檢查它們是否經得住別人的詳細審查。那是學習過程的一部分。但目標應該是為了獲得知識,而不應該是為了說服別人相信你自己是對的。有時候,那隻不過意味著停下來,好好地想一想。

吃瓜網友的收穫

這裡的吃瓜網友就是指我啦。

寫這篇文章我的收穫還是挺大的,首先我買了長髮哥的《資料密集型應用系統設計》一書,讀了幾節,發現這書是真的不錯,豆瓣評分9.6,推薦。

其次完成了這周的周更任務,雖然寫的很艱難,從週六中午,寫到週日凌晨3點。。。

然後還吃到了另外的一個瓜,可謂是瓜中瓜。

這週五的時候 Redis 官網不是出現了短暫的當機嗎,當機其實也沒啥稀奇的,但是頁面上顯示的是連不上 Redis 。這就有點意思了。

我在寫這篇文章的時候,在捲髮哥的某社交平臺上發現了這個:

我關心的並不是 OOM,而是捲髮哥居然讓 Redis 官網執行在一臺一個月僅 5 美元,記憶體只有 1G 的虛擬機器上。哈哈哈,震驚,這瓜味道不錯。

最後,由於捲髮哥是個義大利人,由於最近疫情,四川專家組馳援義大利的事,big thank 中國人。其實這個網友的回答挺好的:投桃報李。

疫情早點過去吧,世界和平。

最後說一句(求關注)

我寫到這裡的時候,不知不覺已經凌晨3點多了,但是因為一直跟著這兩位大神的激烈討論,我的思維異常的清晰。

寫完之後我也說不出誰對誰錯。我覺得對於系統的設計,每個人的出發點都不一樣,沒有完美的架構,沒有普適的架構,但是在完美和普適能平衡的很好的架構,就是好的架構。

瞟了一眼文章字數,快突破了1.2w字。可能又是一篇寫了沒人看的勸退文吧,但是沒有關係,只要有一個人看了我的文章覺得有幫助就行。

點個贊吧,寫文章很累的,不要白嫖我,需要一點正反饋。

才疏學淺,難免會有紕漏,如果你發現了錯誤的地方,還請你留言給我指出來,我對其加以修改。(我每篇技術文章都有這句話,我是認真的說的。)

感謝您的閱讀,我堅持原創,十分歡迎並感謝您的關注。

我是why技術,一個不是大佬,但是喜歡分享,又暖又有料的四川好男人。

參考連結
1.http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
2.https://redis.io/topics/distlock
3.http://antirez.com/news/101
4.https://www.jianshu.com/p/dd66bdd18a56
5.《資料密集型應用系統設計》

歡迎關注公眾號【why技術】,堅持輸出原創。分享技術、品味生活,願你我共同進步。

相關文章