簡介
ZooKeeper 是什么
ZooKeeper 是開源的分布式協(xié)調(diào)服務(wù),由雅虎創(chuàng)建,Google Chubby 的開源實(shí)現(xiàn)。它的設(shè)計(jì)目標(biāo)是封裝那些復(fù)雜且容易出錯(cuò)的分布式一致性服務(wù),構(gòu)成一個(gè)高效可靠的原語集,然后提供簡單易用的接口給用戶使用。
設(shè)計(jì)目標(biāo)
ZooKeeper 是一個(gè)典型的分布式數(shù)據(jù)一致性解決方案,ZooKeeper 主要有四個(gè)設(shè)計(jì)目標(biāo):
順序一致性
從同一個(gè)客戶端發(fā)起的事務(wù)請(qǐng)求,最終將會(huì)嚴(yán)格按照其發(fā)起順序被應(yīng)用到 ZooKeeper 中去。
簡單的數(shù)據(jù)模型
ZooKeeper 通過一個(gè)共享的,樹形結(jié)構(gòu)名字空間來進(jìn)行相互協(xié)調(diào)。樹形結(jié)構(gòu)的數(shù)據(jù)模型,是由一系列 ZNode 數(shù)據(jù)節(jié)點(diǎn)組成,它類似于文件系統(tǒng),Znode 之間的層級(jí)關(guān)系,就像文件系統(tǒng)的目錄結(jié)構(gòu)一樣。
構(gòu)建集群
一個(gè) ZooKeeper 集群通常由多臺(tái)機(jī)器組成,一般是 3 ~ 5 臺(tái)機(jī)器(因?yàn)橹俨媚J?,一般選用奇數(shù)臺(tái)機(jī)器)就可以組成一個(gè)可用的 ZooKeeper 集群,集群中每臺(tái)機(jī)器被賦有不同角色:Leader,F(xiàn)ollower, Observer。
組成 ZooKeeper 的每臺(tái)機(jī)器都會(huì)在 內(nèi)存 中維護(hù)當(dāng)前 ZooKeeper 的樹形結(jié)構(gòu),并且每臺(tái)機(jī)器之間都互相保持通信,同步系統(tǒng)數(shù)據(jù)。
ZooKeeper 的客戶端會(huì)選擇和其中任何一臺(tái)機(jī)器創(chuàng)建一個(gè) TCP 連接,而當(dāng)這個(gè)連接斷開后,客戶端會(huì)自動(dòng)連接到集群中的其他機(jī)器。
高性能
機(jī)器將全量數(shù)據(jù)存儲(chǔ)在內(nèi)存中,并直接服務(wù)于客戶端的所有非事物請(qǐng)求(只讀請(qǐng)求),它非常適合于以讀操作為主的應(yīng)用場景。
不適用
因?yàn)?ZooKeeper 是將全量數(shù)據(jù)(協(xié)同數(shù)據(jù))存儲(chǔ)在內(nèi)存中以提高性能。所以 ZooKeeper 不適合用作海量的數(shù)據(jù)存儲(chǔ),最佳實(shí)踐應(yīng)該將應(yīng)用數(shù)據(jù)和協(xié)同數(shù)據(jù)獨(dú)立存儲(chǔ)。
架構(gòu)
仲裁模式
ZooKeeper 系統(tǒng)可以運(yùn)行于兩種模式之下:獨(dú)立模式(standalone)和仲裁模式(quorum)。獨(dú)立模式是一個(gè)單獨(dú)的服務(wù)器,ZooKeeper 狀態(tài)無法復(fù)制,通常用于開發(fā)階段。
生產(chǎn)中 ZooKeeper 的機(jī)器集群運(yùn)行于仲裁模式。機(jī)器之間可以進(jìn)行狀態(tài)復(fù)制,同時(shí)為客戶端的請(qǐng)求服務(wù)。
仲裁模式下,ZooKeeper 復(fù)制集群中所有機(jī)器的數(shù)據(jù)樹。但是如果讓一個(gè)客戶端等待每個(gè)服務(wù)器完成數(shù)據(jù)保存后再繼續(xù),請(qǐng)求的延遲問題將無法接受。為了解決這個(gè)問題,系統(tǒng)會(huì)指定一個(gè)服務(wù)器的最小數(shù)量,來使 ZooKeeper 可以有效運(yùn)行,這個(gè)數(shù)量一般叫做“法定人數(shù)”。
若客戶端提交某個(gè)寫操作,Leader 會(huì)將該操作提議到 Follower,如果達(dá)到法定數(shù)量的 Follower 認(rèn)可這個(gè)操作,就可認(rèn)為該操作寫成功。法定人數(shù)的大小至關(guān)重要。
如果法定人數(shù)過小,那么當(dāng)產(chǎn)生網(wǎng)絡(luò)分區(qū)時(shí),會(huì)產(chǎn)生每個(gè)分區(qū)下 ZooKeeper 都可以正常獨(dú)立運(yùn)行,整個(gè)系統(tǒng)的狀態(tài)產(chǎn)生不一致,也就是腦裂。假設(shè)有 5 臺(tái)機(jī)器構(gòu)成 ZooKeeper 集群,法定人數(shù)設(shè)置成 2,如果通信故障,將集群分割成兩個(gè)分區(qū),分別包含 2 臺(tái)機(jī)器和 3 臺(tái)機(jī)器,因?yàn)闄C(jī)器數(shù)量都不小于法定人數(shù),所以能獨(dú)立運(yùn)行
如果法定人數(shù)過大,那么會(huì)降低集群可靠性和分區(qū)容錯(cuò)。假設(shè)有 5 臺(tái)機(jī)器的 ZooKeeper 集群,如果法定人數(shù)設(shè)置為 4,那么只允許 1 臺(tái)機(jī)器發(fā)生故障。
ZooKeeper 的法定人數(shù)可配置,默認(rèn)采用多數(shù)原則。為了提高可靠性,一般采用奇數(shù)個(gè)機(jī)器組成集群。
集群角色
ZooKeeper 沒有選用典型的 Master / Slave 模式,而是引入了 Leader,F(xiàn)ollower 和 Observer 三種角色。
Leader
也叫群首,Leader 的作用主要是對(duì)客戶端發(fā)起的寫請(qǐng)求進(jìn)行排序,包括:create,setData 和 delete 操作。Leader 將每一個(gè)請(qǐng)求轉(zhuǎn)換成一個(gè)事務(wù),將這些事務(wù)發(fā)送給 Follower 和 Observer,確保集群按照 Leader 確定的書訊接受并處理這些事務(wù)。當(dāng)然 Leader 也提供讀服務(wù)。
Follower
除了 Leader,其他機(jī)器都屬于 Follower 和 Observer。Follower 也叫跟隨者,它們只提供讀服務(wù),但是參與仲裁,也就是說會(huì)進(jìn)行 Leader 選舉過程和寫操作的仲裁。
Observer
也叫觀察者,只提供讀服務(wù)。它不參與 Leader 選舉和寫操作仲裁。僅僅復(fù)制 Leader 發(fā)布的狀態(tài)變更。
Observer 的主要作用是提高讀請(qǐng)求的可擴(kuò)展性。通過加入多個(gè) Observer,我們可以在不犧牲寫操作吞吐率的前提下提供更多的讀服務(wù)。
會(huì)話(Session)
會(huì)話概念是對(duì)于集群和客戶端而言的,在對(duì) ZooKeeper 執(zhí)行任何操作前,一個(gè)客戶端必須先與服務(wù)器建立會(huì)話。客戶端提交給 ZooKeeper 的所有操作均關(guān)聯(lián)在一個(gè)會(huì)話上。會(huì)話提供了順序保證,同一個(gè)會(huì)話的請(qǐng)求會(huì)以先進(jìn)先出的順序執(zhí)行。
設(shè)置會(huì)話是會(huì)有 sessionTimeout 這個(gè)參數(shù)設(shè)置一個(gè)客戶端會(huì)話的超時(shí)時(shí)。當(dāng)由于服務(wù)器壓力太大,網(wǎng)絡(luò)故障或者是其他原因?qū)е驴蛻舳藬嚅_連接,只要在 sessionTimeout 時(shí)間內(nèi)能重新連上集群中任意一臺(tái)服務(wù)器,那么之前這個(gè)會(huì)話仍然有效。
設(shè) sessionTimeout 為 t,ZooKeeper 客戶端這邊,經(jīng)過 t / 3 時(shí)間內(nèi)未收到消息,客戶端會(huì)向服務(wù)器發(fā)送心跳。在經(jīng)過 2t / 3 時(shí)間后,客戶端開始找尋其他的服務(wù)器,此時(shí)它還有 t / 3 時(shí)間去尋找。
重連時(shí),客戶端不能連接到這樣的服務(wù)器:它未發(fā)現(xiàn)更新而客戶端已經(jīng)發(fā)現(xiàn)更新。ZooKeeper 通過比較事務(wù)id(zxid),來判斷服務(wù)器是否合格。
基本概念
數(shù)據(jù)節(jié)點(diǎn)(znode)
ZooKeeper 將所有數(shù)據(jù)存儲(chǔ)在內(nèi)存中,稱之為“znode”。數(shù)據(jù)模型是一棵樹(znode tree),由斜杠(/)進(jìn)行分割的路徑,就是znode。每個(gè) znode 上都會(huì)保存自己的數(shù)據(jù)內(nèi)容,同時(shí)還會(huì)保存一些列屬性信息。

如上圖所示,NameService,Server1,Server2,Configuration 等每個(gè)都是一個(gè) znode。
持久節(jié)點(diǎn)和臨時(shí)節(jié)點(diǎn)
根據(jù)持久性分,znode 可以分為:
- 持久節(jié)點(diǎn)(persistent)。持久節(jié)點(diǎn)只能通過 delete 接口才能刪除;
- 臨時(shí)節(jié)點(diǎn)(ephemeral)。臨時(shí)節(jié)點(diǎn)不僅通過 delete,當(dāng)創(chuàng)建該節(jié)點(diǎn)的客戶端會(huì)話過期或關(guān)閉后,節(jié)點(diǎn)也會(huì)被刪除。
有序節(jié)點(diǎn)
一個(gè) znode 還能設(shè)置為有序節(jié)點(diǎn)(sequential)。當(dāng)創(chuàng)建有序節(jié)點(diǎn)時(shí),一個(gè)序號(hào)會(huì)被追加到路徑之后。例如,當(dāng)客戶端創(chuàng)建一個(gè)有序節(jié)點(diǎn) /tasks/task-,那么 ZooKeeper 會(huì)分配一個(gè)序號(hào),如 1,并將該序號(hào)追加到路徑之后,所以這個(gè) znode,最后會(huì)是 /tasks/task-1,這個(gè)序號(hào)是由“父節(jié)點(diǎn)”維護(hù)的自增數(shù)字。
版本
znode 上都會(huì)存儲(chǔ)數(shù)據(jù),對(duì)于每個(gè) znode,ZooKeeper 都會(huì)維護(hù)一個(gè)叫做 Stat 的數(shù)據(jù)結(jié)構(gòu),Stat 中記錄了 znode 的三個(gè)數(shù)據(jù)版本,分別是:
- version,當(dāng)前 znode 版本。
- cversion,當(dāng)前 znode 子節(jié)點(diǎn)的版本。
- aversion,當(dāng)前 znode ACL 版本。
版本信息表示對(duì)數(shù)據(jù)節(jié)點(diǎn)的數(shù)據(jù)內(nèi)容,子節(jié)點(diǎn)列表,或節(jié)點(diǎn) ACL 信息的修改次數(shù)。版本信息用來實(shí)現(xiàn)樂觀鎖機(jī)制的“寫入校驗(yàn)”。
如果 version 為“-1”,則說明客戶端并不要求使用樂觀鎖,寫操作時(shí)忽略版本比較。
如果 version 不為 “-1”,那么就要對(duì)比客戶端寫操作時(shí)攜帶的 version,和 znode 當(dāng)前的版本,如果兩個(gè)版本不匹配,就拋出異常,拒絕執(zhí)行寫操作。
監(jiān)視點(diǎn)(Watcher)和通知(Notification)
ZooKeeper 提供遠(yuǎn)程服務(wù)的方式被訪問,如果通過輪詢的方式不斷查詢 ZooKeeper 的 znode tree 狀態(tài),沒有必要且導(dǎo)致較高的延遲。為了替代輪詢操作,ZooKeeper 提供基于通知(notification)的機(jī)制:客戶端向 ZooKeeper 注冊(cè)需要接收通知的 znode,通過對(duì) znode 設(shè)置監(jiān)視點(diǎn)(watcher)來接受通知。
監(jiān)視點(diǎn)是單次出發(fā)的操作,即一次通知后,該監(jiān)視點(diǎn)就作廢。所以為了接收多次通知,客戶端必須每次通知后設(shè)置一個(gè)新的監(jiān)視點(diǎn)。
為了保證沒有數(shù)據(jù)變更被遺漏,通常操作時(shí),會(huì)在設(shè)置監(jiān)視點(diǎn)之前,先讀一次 znode 的狀態(tài)。
內(nèi)容來源
從 Paxos 到 ZooKeeper 分布式一致性原理與實(shí)踐
ZooKeeper 分布式過程協(xié)同技術(shù)詳解