Redis RedLock 完美的分散式鎖麼?

犀利豆發表於2017-10-29

原文地址

上週花了點時間研究了 Redis 的作者提的 RedLock 的演算法來實現一個分散式鎖,文章地址。在官方的文件最下面發現了這樣一句話。

Analysis of RedLock

Martin Kleppmann analyzed Redlock here. I disagree with the analysis and posted my reply to his analysis here.

突然覺得事情好像沒有那麼簡單,就點進去看了看。仔細讀了讀文章,發現了一個不得了的世界。於是靜下心來研究了 Martin 對 RedLock 的批評,還有 RedLock 作者 antirez 的反擊。

Martin 的批評

Martin上來就問,我們要鎖來幹啥呢?兩個原因:

  1. 提升效率,用鎖來保證一個任務沒有必要被執行兩次。比如(很昂貴的計算)
  2. 保證正確,使用鎖來保證任務按照正常的步驟執行,防止兩個節點同時操作一份資料,造成檔案衝突,資料丟失。

對於第一種原因,我們對鎖是有一定寬容度的,就算髮生了兩個節點同時工作,對系統的影響也僅僅是多付出了一些計算的成本,沒什麼額外的影響。這個時候 使用單點的 Redis 就能很好的解決問題,沒有必要使用RedLock,維護那麼多的Redis例項,提升系統的維護成本。

對於第二種原因,對正確性嚴格要求的場景(比如訂單,或者消費),就算使用了 RedLock 演算法仍然不能保證鎖的正確性。

我們分析一下 RedLock 的有啥缺陷吧:

unsafe-lock
unsafe-lock

作者 Martin 給出這張圖,首先我們上一講說過,RedLock中,為了防止死鎖,鎖是具有過期時間的。這個過期時間被 Martin 抓住了小辮子。

  • 如果 Client 1 在持有鎖的時候,發生了一次很長時間的 FGC 超過了鎖的過期時間。鎖就被釋放了。
  • 這個時候 Client 2 又獲得了一把鎖,提交資料。
  • 這個時候 Client 1 從 FGC 中甦醒過來了,又一次提交資料。

這還了得,資料就發生了錯誤。RedLock 只是保證了鎖的高可用性,並沒有保證鎖的正確性。

這個時候也許你會說,如果 Client 1 在提交任務之前去查詢一下鎖的持有者是不自己就能解決這個問題?
答案是否定的,FGC 會發生在任何時候,如果 FGC 發生在查詢之後,一樣會有如上討論的問題。

那換一個沒有 GC 的程式語言?
答案還是否定的, FGC 只是造成系統停頓的原因之一,IO或者網路的堵塞或波動都可能造成系統停頓。

文章讀到這裡,我都絕望了,還好 Martin給出了一個解決的方案:

fencing-tokens
fencing-tokens

為鎖增加一個 token-fencing。

  • 獲取鎖的時候,還需要獲取一個遞增的token,在上圖中 Client 1 還獲得了一個 token=33的 fencing。
  • 發生了上文的 FGC 問題後,Client 獲取了 token=34 的鎖。
  • 在提交資料的時候,需要判斷token的大小,如果token 小於 上一次提交的 token 資料就會被拒絕。

我們其實可以理解這個 token-fencing 就是一個樂觀鎖,或者一個 CAS。

Martin 還指出了,RedLock 是一個嚴重依賴系統時鐘的分散式系統。

還是這個過期時間的小辮子。如果某個 Redis Master的系統時間發生了錯誤,造成了它持有的鎖提前過期被釋放。

  • Client 1 從 A、B、D、E五個節點中,獲取了 A、B、C三個節點獲取到鎖,我們認為他持有了鎖
  • 這個時候,由於 B 的系統時間比別的系統走得快,B就會先於其他兩個節點優先釋放鎖。
  • Clinet 2 可以從 B、D、E三個節點獲取到鎖。在整個分散式系統就造成 兩個 Client 同時持有鎖了。

這個時候 Martin 又提出了一個相當重要的關於分散式系統的設計要點:

好的分散式系統應當是非同步的,且不能時間作為安全保障的。因為在分散式系統中有會程式暫停,網路延遲,系統時間錯誤,這些因數都不能影響分散式系統的安全性,只能影響系統的活性(liveness property)。換句話說,就是在極端情況下,分散式系統頂多在有限的時間內不能給出結果,但是不能給出錯誤的結果

所以總結一下 Martin 對 RedLock 的批評:

  • 對於提升效率的場景下,RedLock 太重。
  • 對於對正確性要求極高的場景下,RedLock 並不能保證正確性。

這個時候感覺醍醐灌頂,簡直寫的太好了。

RedLock 的作者,同時也Redis 的作者對 Martin的文章也做了迴應,條理也是相當的清楚。

antirez 的迴應

antirez 看到了 Martin 的文章以後,就寫了一篇文章回應。劇情會不會反轉呢?

antirez 總結了 Martin 對 RedLock的指控:

  1. 分散式的鎖具有一個自動釋放的功能。鎖的互斥性,只在過期時間之內有效,鎖過期釋放以後就會造成多個Client 持有鎖。
  2. RedLock 整個系統是建立在,一個在實際系統無法保證的系統模型上的。在這個例子中就是系統假設時間是同步且可信的。

對於第一個問題:
antirez 洋洋灑灑的寫了很多,仔細看半天,也沒有解決我心中的疑問。回顧一下RedLock 獲取鎖的步驟:

  1. 獲取開始時間
  2. 去各個節點獲取鎖
  3. 再次獲取時間。
  4. 計算獲取鎖的時間,檢查獲取鎖的時間是否小於獲取鎖的時間。
  5. 持有鎖,該幹啥幹啥去

如果,程式在1-3步之間發生了阻塞,RedLock可以感知到鎖已經過期,沒有問題。
如果,程式在第 4 步之後發生了阻塞?怎麼辦???
答案是,其他具有自動釋放鎖的分散式鎖都沒辦解決這個問題

對於第二個指控:
antirez 認為,首先在實際的系統中,從兩個方面來看:

  1. 系統暫停,網路延遲。
  2. 系統的時間發生階躍。

對於第一個問題。上文已經提到了,RedLock做了一些微小的工作,但是沒辦法完全避免。其他帶有自動釋放的分散式鎖也沒有辦法。

第二個問題,Martin認為系統時間的階躍主要來自兩個方面:

  1. 人為修改。
  2. 從NTP服務收到了一個跳躍時時鐘更新。

對於人為修改,能說啥呢?人要搞破壞沒辦法避免。
NTP受到一個階躍時鐘更新,對於這個問題,需要通過運維來保證。需要將階躍的時間更新到伺服器的時候,應當採取小步快跑的方式。多次修改,每次更新時間儘量小。**

說個題外話,讀到這裡我突然理解了運維同學的郵件:

Screenshot 2017-10-29 3.43.22
Screenshot 2017-10-29 3.43.22

所以嚴格來說確實, RedLock建立在了 Time 是可信的模型上,理論上 Time 也是發生錯誤,但是在現實中,良好的運維和工程一些機制是可以最大限度的保證 Time 可信。

最後, antirez 還打出了一個暴擊,既然 Martin 提出的系統使用 fecting token 保證資料的順序處理。還需要 RedLock,或者別的分散式鎖 幹啥??

回顧

看完二人的部落格來往,感覺就是看武俠戲裡面的高手過招,相當得爽快。二人思路清晰,Martin 上來就看到RedLock的死穴,一頓猛打,antirez見招拆招成功化解。
至於二人誰對誰錯?
我覺得,每一個系統設計都有自己的側重或者侷限。工程也不是完美的。在現實中工程中不存在完美的解決方案。我們應當深入瞭解其中的原理,瞭解解決方案的優缺點。明白選用方案的侷限性。是否可以接受方案的侷限帶來的後果。
架構本來就是一門平衡的藝術。

最後

Martin 推薦使用ZooKeeper 實現分佈事務鎖。Zookeeper 和 Redis的鎖有什麼區別? Zookeeper解決了Redis沒有解決的問題了麼?且聽下回分解。

參考:

  1. Distributed locks with Redis
  2. How to do distributed locking
  3. Is Redlock safe?
  4. 基於Redis的分散式鎖到底安全嗎(上)?

相關文章