Zookeeper vs Chubby
上一篇博客Chubby的鎖服務中已經對Chubby的設計和實現做了比較詳細的實現,但由於其閉源身份,工程中接觸比較多的還是它的一個非常類似的開源實現Zookeeper。Zookeeper作為後起之秀,應該對Chubby有很多的借鑒,他們有眾多的相似之處,比如都可以提供分散式鎖的功能;都提供類似於UNIX文件系統的數據組織方式;都提供了事件通知機制Event或Watcher;都在普通節點的基礎上提供了臨時節點來滿足服務存活發現的功能;都以集群的方式提供服務;都通過選舉產生Master並在集群間以Quorum的方式副本數據。但他們並不完全相同,並且Zookeeper還擁有後發優勢,本文將重點介紹他們之間的區別,並試著分析這些區別的原因和結果。
區別根源
一個設計良好的系統應該是圍繞並為其設計目標服務的,因此通過對Chubby及Zookeeper的目標比較或許能對其區別的本質略窺一二:
- Chubby:provide coarse-grained locking as well as reliable storage for a loosely-coupled distributed system.
- Zookeeper:provide a simple and high performance kernel for building more complex coordination primitives at the client.
可以看出,Chubby旗幟鮮明的表示自己是為分散式鎖服務的,而Zookeeper則傾向於構造一個「Kernel」,而利用這個「Kernel」客戶端可以自己實現眾多更複雜的分散式協調機制。自然的,Chubby傾向於提供更精準明確的操作來免除使用者的負擔,Zookeeper則需要提供更通用,更原子的原材料,留更多的空白和自由給Client。也正是因此,為了更適配到更廣的場景範圍,Zookeeper對性能的提出了更高的要求。
比較
圍繞上述思路,接下來將從一致性、分散式鎖的實現及使用、客戶端Cache等方面來對比他們的不同。
一致性
- Chubby:線性一致性(Linearizability)
- Zookeeper:寫操作線性(Linearizable writes) + 客戶端有序(FIFO client order)
Chubby所要實現的一致性是分散式系統中所能實現的最高級別的一致性,簡單的說就是每次操作時都可以看到其之前的所有成功操作按順序完成,而Zookeeper將一致性弱化為兩個保證,其中寫操作線性(Linearizable writes)指的是所有修改集群狀態的操作按順序完成,客戶端有序(FIFO client order)指對任意一個client來說,他所有的讀寫操作都是按順序完成。從實現上來看:
- Chubby的所有讀寫請求都需要交給Leader串列執行,並且Leader會用一致性協議複製到集群所有節點。
- Zookeeper僅將寫操作交給Leader串列執行,也就保證了寫操作線性。對於讀操作,則由與客戶端連接的Server自行處理,客戶端有序的保證也很簡單,Zookeeper給每個寫入後的狀態一個唯一自增的Zxid,並通過寫請求的答覆告知客戶端,客戶端之後的讀請求都會攜帶這個Zxid,直連的Server通過比較Zxid判斷自己是否滯後,如果是則讓讀操作等待。
對比Chubby及Zookeeper的一致性保證可以看出,Zookeeper損失的是不同客戶端的讀寫操作的一致性,如下圖所示:
Zookeeper集群的初始狀態為x;Client A發起寫操作將狀態修改為y,寫操作由於寫操作線性的保證轉發給Leader通過一致性協議複製到整個集群,過半數節點成功後返回成功;此時ClientB讀還未同步到的Server,獲得x。這種一致性的損失,換來的是集群讀請求的高性能。對於不能容忍這種不一致的場景,Zookeeper提供兩種機制滿足:
- Watcher通知跟Read操作一樣是由客戶端鎖連接Server本地處理的,所以Client B收到對應的事件通知後再Read就一定能看到最新的狀態y;
- 由於客戶端有序的保證,Client B可以在Read操作前加一條Write操作,來保證看到最新狀態,為了避免這個不必要的Write操作Zookeeper提供Sync命令,相當於一條空的寫操作。
這也符合Zookeeper的設計思路:提供更高效更原子的操作,通過這些操作客戶端可以自行組裝滿足各種需求,即便是對一致性要求更高的需求。
分散式鎖
上一篇博客Chubby和鎖服務中已經分析了Chubby的分散式鎖的設計實現,分散式鎖從實現到使用認為可以分為一致性協議,鎖的實現和鎖的使用三個部分,相對於Chubby,Zookeeper傾向於實現更少的部分,而將更多的選擇交給用戶:
- Chubby:提供準確語義的Lock,Release操作,內部完成了一致性協議,鎖的實現的內容,僅將鎖的使用部分留給用戶;
- Zookeeper:並沒有提供加鎖放鎖操作,用戶需要利用Zookeeper提供的基礎操作,完成鎖的實現和鎖的使用部分的內容,如下圖所示:
因為如此,用戶在使用Zookeeper來獲得鎖功能的時候會稍顯複雜,以讀寫所為例,Chubby可以通過介面直接使用,而Zookeeper需要的操作如下:
Write Lockn1 n = create(l + 「/write-」, EPHEMERAL|SEQUENTIAL) n2 C = getChildren(l, false)n3 if n is lowest znode in C, exitn4 p = znode in C ordered just before nn5 if exists(p, true) wait for event n6 goto 2nnRead Lockn1 n = create(l + 「/read-」, EPHEMERAL|SEQUENTIAL)n2 C = getChildren(l, false)n3 if no write znodes lower than n in C, exitn4 p = write znode in C ordered just before nn5 if exists(p, true) wait for eventn6 goto 3n
用戶需要在某個ZNode下創建代表自己的臨時子節點來搶寫鎖,同時這個子節點有一個自增的編號,編號最小的節點獲得寫鎖,其他節點關注其前一個ZNode的存在,如果消失會收到watcher通知,之後繼續嘗試加寫鎖;讀鎖稍寬鬆,只要沒有比自己編號更小的寫節點就可以加讀鎖成功。
客戶端Cache
- Chubby:內部維護,一致性cache;
- Zookeeper:Client自己實現,通過watcher控制;
Chubby和鎖服務介紹過,Chubby通過Server和客戶端Lib的配合在內部維護了完整的客戶端緩存功能,並且這個客戶端緩存是一致性的,這就極大的簡化了用戶的使用成本,因為用戶根本不需要知道Cache的存在。相對應,Zookeeper根本沒有實現Cache功能,用戶如果需要必須自己實現,利用watcher機制,用戶能方便的按自己需求實現一致或不一致的Cache語義。
API
比較Chubby和Zookeeper的API設計可以看出Zookeeper圍繞自己設計目標的介面設計:
- Zookeeper取消Handle,因此省略了Open,Close介面,這就要求所有對ZNode的訪問都需要提供完整的Path。這樣是很合理的,因為Zookeeper定位提供基礎介面,那麼上層使用時很有可能是需要很多個ZNode配合完成的,從上面介紹的鎖的實現便可以看出,這樣一來維護多個Handle反而造成了使用負擔;
- Zookeeper沒有提供Lock,Release等精確語義的鎖操作;
- Zookeeper提供Sync操作來滿足對更高的一致性要求的場景。
總結
通過上面的分析可以看出Chubby和Zookeeper設計定位的區別,以及為了各自目標作出的設計實現的努力。Chubby追求使用簡單,Zookeeper追求使用自由,簡單就一定有更多的限制,自由就一定更多的使用成本,究竟孰好孰壞,就是個見仁見智的問題了。其實類似的權衡在計算機科學中是非常常見,比如庫或框架的設計,比如高級語言的語法設計。我們的分散式配置管理QConf其實就是利用Zookeeper功能,針對配置場景實現了自己的功能,後續我們也會考慮基於一致性庫Floyd實現更加簡單易用的協調服務替代Zookeeper,來極大的減輕QConf客戶端的部署及使用負擔。
參考
The Chubby lock service for loosely-coupled distributed systems
ZooKeeper: Wait-free coordination for Internet-scale systems
Talk about consensus algorithm and distributed lock
Zookeeper vs Chubby
Zookeeper
QConf
Floyd
從配置文件到分散式配置管理QConf
推薦閱讀:
※zookeeper與keepalived的高可用區別?
※zookeeper在dubbo到底起了什麼作用,dubno如何解決了阿里的高並發問題?