ZooKeeper 我想大家應該都略有耳聞,可能你在開發中沒有直接使用過,但常用的 Hadoop、HBase、Kafka、Dubbo 等都有使用到 ZooKeeper。那 ZooKeeper 到底起到了什么樣的作用,為什么這些框架、系統需要使用 ZooKeeper呢,我們在開發過程中應該如何使用 ZooKeeper,又是否有 ZooKeeper的替代品呢。本文將圍繞以上問題,從以下三方面說起:
- 來源與作用;
- 經典應用場景;
- 替代品。
1. 來源與作用
ZooKeeper 的設計初衷是什么?這要從雅虎的一個研究小組說起。當時,研究人員發現雅虎內部的很多分布式系統都需要依賴一個組件進行分布式協調,但是這些組件往往都存在分布式單點問題。所以雅虎便組織開發了一個通用的無單點問題的分布式協調框架,那就是 ZooKeeper,一方面解決單點問題,另一方面,將分布式協調從分布式系統中抽離出來,讓開發者更專注於業務邏輯。
下面分別對 “單點問題” 和 “分布式協調” 進行講述。
1.1 單點問題
單點問題(又叫單點故障,Single Point of Failure,SPOF)是指在系統中一旦失效就會讓整個系統無法運作的部件。舉個例子,將系統只部署在機器 A 一台機器上,如果機器 A 失效,則整個系統將無法運作。而為了解決該問題,一般采用冗余的方式,增加多台機器,只要多台機器不同時失效,則系統將可正常運作。
ZooKeeper 也是通過集群的方式解決單點問題。看似通過集群的方式,多部署幾台機器就能解決單點問題。首先我們需要把單點問題細化成無狀態的單點問題和有狀態的單點問題。這里說的有無狀態可以參考網絡協議中的概念,HTTP 無狀態協議,每次請求都是獨立的,TCP 有狀態協議,依靠記錄狀態完成可靠傳輸。
對於簡單的無狀態的單點問題,通過集群的方式便能解決,例如,代理節點做消息轉發,這就是無狀態的情況,如下圖所示:

如果消息轉發服務器 X 宕機了,還有服務器 Y 能繼續正常工作,而且機器數量越多可用性越高。
對於有狀態的情況,則不一樣了,將會面臨分布式一致性問題。該問題僅靠簡單的增加機器是無法解決的。常用的解決方法有:
- 去狀態:將問題去除為無狀態,例如將狀態存儲到可靠的DB中;
- 主從:由 Master 做主要的數據處理,Slaver 同步 Master 的狀態,例如 MySQL 的主從復制,Master 處理寫操作,Slaver 通過 binlog 同步狀態。

上述簡單介紹了 MySQL 的解決方法,而 ZooKeeper 是使用的 ZAB 協議實現強一致性。關於分布式一致性問題,本文將不繼續敘述了,大家就先記住 ZooKeeper 解決了單點問題,能保證強一致性,不用擔心 ZooKeeper 集群會因為有機器宕機導致數據不一致性問題。
聊完了單點問題,下面聊下分布式協調,這也是 ZooKeeper 所需要起到的作用。
1.2 分布式協調
分布式協調,其含義正如它的字面意思,在分布式環境下進行協調。舉個例子,在單機情況下,若多個線程同時想對某個資源進行修改,例如庫存減一。為了保證正確性,我們會在這個資源上加鎖。但是在分布式情況下,若多台機器想對某個資源進行修改,我們如何為這個資源加鎖呢?這時候就需要一個協調者的出現。機器若想知道其他機器有沒有加鎖,不需要去和其他機器通信,只需和協調者通信就可以知道資源的加鎖情況了,也可以認為鎖由協調者進行管理。

你可能會說為什么要讓 ZooKeeper 擔任協調者, 為什么不直接在集群里選一台機器。是的,當然可以,但是這樣的話你就需要解決協調者的單點問題(擔任協調者的機器宕機)。又因為分布式系統中大多都需要這么一個協調者組件,而為了復用,將其抽離出來,於是就有了分布式協調系統。因此 Dubbo、Kafak 等框架在實現分布式時,直接把 ZooKeeper 拿來用,這樣就不用再重復實現協調者組件了。而程序員的我們在分布式開發中也只需要關注業務邏輯實現即可。
總結下,ZooKeeper 的作用就是提供無單點問題的分布式協調服務。
2. 經典應用場景
本節將介紹一些 ZooKeeper 的經典應用場景,看看到底怎么使用 ZooKeeper,但首先還需要對 ZooKeeper 的數據模型、節點特性、Watcher 機制有一定認識,下面會簡單介紹一下。
2.1 ZooKeeper 基礎
什么是數據模型,可以理解為數據的存放形式,例如 Redis 是以鍵值對的形式存儲數據,而 ZooKeeper 是樹的形式,如下圖所示:

其中根節點的名字是 “/”,根節點有兩個子節點 “A” 和 “B”,“A” 又有三個子節點 “X”、“Y”、“Z”。我們可以根據需要組織樹的節點,還可以在節點上存儲數據,是不是有點像文件系統的感覺。
對數據模型有了基本認識,還需要知道下節點的特性,有 4 種節點,都很好理解:
- 持久節點:創建后將一直存在,除非主動刪除;
- 持久順序節點:其持久性與持久節點一樣,但是它具有順序性,節點名上加上一個數字后綴表示其創建的順序,例如 1,2,3,4...;
- 臨時節點:如果創建該節點的會話失效,那該節點也將被刪除;
- 臨時順序結點:在臨時節點上增加了順序的概念。
最后再知道下 Watcher 機制,就是一種監聽機制,我們可以監聽某個節點數據內容的變化、子節點變化等,一旦發生了我們監聽的事件,ZooKeeper 將會通知我們。
介紹完 ZooKeeper 的一些基礎,下面進入本節的正題。
2.2 數據發布/訂閱
通過 Watcher機制 實現數據發布/訂閱。以配置中心場景為例。將配置發布到 ZooKeeper 的節點上,其他機器可通過監聽 ZooKeeper 上節點的變化來實現配置的動態更新。例如我們把數據庫配置放在 “/configserver/app1/database_config” 節點上,然后讓每台機器在啟動時從該節點上獲取數據庫配置信息,並且監聽節點的內容變化,發生配置變更時,重新獲取配置。

2.3 命名服務
通過 ZooKeeper 的順序節點生成全局唯一 ID。例如一個分布式任務調度系統,為任務生產全局唯一 ID,如下圖所示:

在創建完順序節點后,通過節點名拼接可得到唯一 ID 的任務名,例如 “type1-job-000000003”。
2.4 集群管理
通過 ZooKeeper 的臨時節點 和 Watcher 機制,來監控集群的運行狀態,如下圖所示:

當有新的機器加入集群時,就在 “machines” 下創建一個臨時節點。當有機器宕機時,臨時節點將失效被刪除。通過監控 “machines” 下的子節點變化,就能得知集群機器的狀態。
2.5 分布式鎖
分布式鎖,主要有兩類,排他鎖和共享鎖。
排它鎖,在事務對資源的加鎖期間,不允許其他事務進行讀寫操作。通過一個臨時節點便能表示一個鎖,如下圖所示:

在 “resourceA\exclusive_lock” 節點下建立一個 “lock” 節點表示對資源A上了排它鎖。當多台機器嘗試在 “exclusive_lock” 下創建 “lock” 節點時,只會有一台能成功創建,而其他失敗的機器將監聽 “exclusive_lock” 的子節點變化。當鎖被主動釋放時或者宕機時,“lock” 節點將被刪除,其他機器將被通知,繼續嘗試創建 “lock” 節點獲取鎖。
共享鎖,在事務對資源的加鎖期間,允許其他事務進行讀操作。仍然是通過一個節點來代表鎖,不同的地方在於需要區分下讀寫操作,讀寫可以通過節點的數據內容來表示,例如 R 表示讀操作,W 表示寫操作。如下圖所示:

讀寫操作都是在 “shared_lock” 節點下創建一個名為 “lock” 的臨時順序節點,只是數據內容不同。對於讀操作,如果比自己序號小的子節點都是讀請求,則認為自己成功獲得了鎖,可以進行讀操作,如果序號小的節點中包含寫操作,需要進行等待,監聽 “shared_lock” 的子節點變化。對於寫操作,如果自己不是序號最小的子節點,則進入等待,監聽 “shared_lock” 的子節點變化。
3. 替代品
第二節中,我們了解了 ZooKeeper 在分布式環境下有很多的應用場景,那是不是必須使用 ZooKeeper 才能實現分布式鎖、集群管理等功能呢?當然不是的,還有其他技術可供選擇。例如 Redis 也是可以用於分布式協調服務,關於第二節所說的那些場景,都是能實現的,只是會因為它們的數據模型不同,需要采用不同的設計。
那我們是使用 ZooKeeper 還是 Redis 呢?這個需要根據場景來決定,我們在分布式鎖的場景下來對比下這兩者的優缺點:
- Redis:支持高並發的獲取、釋放鎖操作,不能保證 100% 的數據一致性,可能會出現問題(極少);
- ZooKeeper:鎖的模型健壯,強一致性,但是頻繁的申請鎖、釋放鎖操作對 ZooKeeper 集群的壓力較大。
Redis 的數據一致性不如 ZooKeeper,而 ZooKeeper 對高並發的支持不如 Redis。
最后還想介紹下,ectd,這是一個高可用的鍵值存儲系統,使用 Go 編寫,通過 Raft 算法處理日志復制保證強一致性,被 Kubernetes 等系統所使用。與 ZooKeeper 相比的話,有較多技術細節的不同,我舉幾個比較寬泛的,ZooKeeper 是基於 Java 實現的,自然也具有了 Java 的缺點,例如 GC 暫停,而 ectd 沒有 GC 暫停的問題。ZooKeeper 有很好的 Java 客戶端庫,還提供其他語言的客戶端庫,ectd 作為后起之秀,還沒有很好的 Java 客戶端庫,只能通過 HTTP 方式調用。
以上列出 Redis 和 ectd 只是為了幫助讀者開闊視野,不局限於 ZooKeeper。而如何進行具體的技術選型,筆者還缺乏經驗,就不多說了。
4. 參考
- 《從Paxos到ZooKeeper》
- 為什么需要ZooKeeper