昨天,我們介紹了兩種形式的鎖,分別是排他鎖和屏障,並且我們以Redis作為範例來簡單解釋實作細節。
但我們曾一直提及,Redis的持久化很不可靠,為了要實作穩定的同步機制必須藉由很多工程上的方法進行各種配套,而這也是Redlock
的由來。
Redlock
雖然是透過Redis實作,但是Redlock
可以確保非常高的資料一致性同時兼顧吞吐量,因此Redlock
是一個經典的分散式鎖實現。
但是Redlock
有非常高的維護成本,也需要使用者對分散式架構具有一定程度的知識。因此,這篇文章會深入介紹Redlock
並且闡述我對他的想法。
在開始之前今天的主題前,我還是要再一次強調Redis的持久化很不可靠,無論是單機或是主從複製都有一定程度的資料丟失風險。因此,實務上為了確保Redis足夠可用而不需要使用者過度操心,都會以Redis叢集的方式來使用Redis。Redis叢集在持久化上就有很高程度的保證了。
但是,在叢集環境下,一個Master會有許多Slave副本,因此對於我們想做到的同步鎖或說資料強一致性依然有很大的風險。
在Redis的實作下,資料複製是在背景執行而不是主執行緒,因此當客戶端成功寫入,也不代表資料已經成功複製了。有一個常見的出問題流程如下。
當客戶端1成功在Master上上鎖,但Master在資料複製前掛掉(或者網路不通,這也稱為分片失效),那麼客戶端2依然可以在Slave上上鎖,因為Slave會被提升為新的Master。
正因如此,即使是使用Redis叢集,在同步鎖定的場景依然不夠可靠。
Redlock
的概念既然我們知道了,無論是單一Redis、主從複製甚至是叢集都有可能具備風險,那我們該如何實作一個可靠的分散式鎖?
答案是,透過多數決。
既然單一Redis不可靠,那麼我們就用好幾個Redis組一個委員會吧!唯有超過半數的委員同意,鎖才會生效,否則鎖被視為無效。
委員會的成員可以是單一Redis、主從複製或者叢集,都可以,但有一個重點,這些委員間不能私相授受。亦即是這些委員間不能屬於同一個叢集也不能互相開啟主從複製,必須是完全獨立的存在才行。
根據多數決規則,委員會必須要有奇數個成員,這樣才有辦法取得多數(
N/2 + 1
)。N
表示委員總數。
完整的實作流程寫在Redis官方文件中的分散式鎖篇章。所以我簡單描述一下流程就好。
假設我們的委員會是由三個Redis組成。
以下流程表示成功上鎖、執行完該做的事並且最後解鎖。
SETNX
。至於沒成功上鎖的流程也是類似的,只要兩個委員回應失敗,鎖就視為失效,那就不能進入臨界區段做事。
重點是,即便沒要到鎖,依然要對全部委員申請解鎖。
Redlock
的問題在描述完Redlock
的流程後,讓我解釋一下為什麼我鮮少考慮這個方案。
首先,這個上鎖和解鎖的過程非常耗時間。舉例來說,假設我們只要上鎖3秒鐘,但我們可能要花1秒以上在做上鎖和解鎖,那實際能做事的時間就更少了。因為無論如何都得要對全部委員發起請求,光是網路延遲和封包遺失等問題就會讓傳輸充滿複雜度和混亂。
其次,為了讓不可靠的Redis盡量可靠,那就必須啟動許多Redis做多數決。站在維運的角度來說,就有這麼多Redis必須要做監控和管理。另外站在開發的角度來說,要如何讓所有想要上鎖的服務都能知道委員會的組成也是個大挑戰。這樣的方案無論維護或開發的複雜度都很高。
再者,這個方案的核心機制是全局唯一ID。如果ID重複,那麼無論上鎖還是解鎖都會產生不可預期的結果。當這重複ID發生,要如何正確偵測到也是個顯著的問題。更重要的是,系統時間在分散式架構中是一個很弱的保證,要如何能夠確保所有系統的時間都是一致的?光是NTP絕對不夠。這在系統設計中稱為「時鐘偏斜」(Clock Skew)。
總結一下,Redlock
是一個昂貴的方案並且具備很高的技術門檻。雖然網路上有根據各程式語言寫出的各種套件,但每個使用者還是得要了解他背後實作的機制和可能背負的風險,而不是單純拿來用即可。
我不使用Redlock
的主因是,為了使不可靠的Redis可靠完全本末倒置。
我總是跟我的組員說「在Redis的資料必須視作隨時會消失」,如果想將資料長久保存,那就應該選擇資料庫而不是快取。
當實作分散式鎖,我傾向不使用Redis而是更可靠的資料庫,例如具有強一致性的MySQL或者我比較偏好的MongoDB。但即便我們使用資料庫,我們依然要注意資料庫的實作細節,例如MongoDB如果要具備線性一致性(Read-after-write Consistency),那就得要正確使用Read Concern
和Write Concern
。
除此之外,還必須思考「這個場景真的需要鎖嗎?」無法透過架構的修改來避免競爭條件嗎?在分散式架構中尋求同步其實是很沒效率的,因此比起鎖,我會選擇透過改造架構的方式來解決原本需要的同步。
如果真的需要用到分散式鎖,應該需要盡可能地減少使用範圍,只有在最核心關鍵處使用,那麼這樣的用量其實資料庫就足以應付了。
在系統設計時有許多需要考慮的因素,而不僅僅只是讓功能可以運作而已。如何控制預算?如何安排人力?如何日常維運?如何排錯?這些因素都取決於團隊的承載量,我相信這些遠比實際功能更重要。
至於Redlock
是一個很複雜的方案,雖然我會用他,但僅在不得不的情況下使用,能避則避。