本來(lái)想系統(tǒng)回顧下 ZooKeeper的,可是網(wǎng)上沒(méi)找到一篇合自己胃口的文章,寫(xiě)的差不多的,感覺(jué)大部分都是基于《從Paxos到ZooKeeper 分布式一致性原理與實(shí)踐》寫(xiě)的,所以自己讀了一遍,加上項(xiàng)目中的使用,做個(gè)整理。加油,奧利給!
前言
面試常常被要求「熟悉分布式技術(shù)」,當(dāng)年搞 “XXX管理系統(tǒng)” 的時(shí)候,我都不知道分布式系統(tǒng)是個(gè)啥。分布式系統(tǒng)是一個(gè)硬件或軟件組件分布在不同的網(wǎng)絡(luò)計(jì)算機(jī)中上,彼此之間僅僅通過(guò)消息傳遞進(jìn)行通信和協(xié)調(diào)的系統(tǒng)。
計(jì)算機(jī)系統(tǒng)從集中式到分布式的變革伴隨著包括分布式網(wǎng)絡(luò)、分布式事務(wù)、分布式數(shù)據(jù)一致性等在內(nèi)的一系列問(wèn)題和挑戰(zhàn),同時(shí)也催生了一大批諸如ACID
、CAP
和 BASE
等經(jīng)典理論的快速發(fā)展。
為了解決分布式一致性問(wèn)題,涌現(xiàn)出了一大批經(jīng)典的一致性協(xié)議和算法,最為著名的就是二階段提交協(xié)議(2PC),三階段提交協(xié)議(3PC)和Paxos
算法。Zookeeper
的一致性是通過(guò)基于 Paxos
算法的 ZAB
協(xié)議完成的。一致性協(xié)議之前的文章也有介紹:「走進(jìn)分布式一致性協(xié)議」從2PC、3PC、Paxos 到 ZAB,這里就不再說(shuō)了。
1. 概述
1.1 定義
ZooKeeper 官網(wǎng)是這么介紹的:”Apache ZooKeeper 致力于開(kāi)發(fā)和維護(hù)一個(gè)支持高度可靠的分布式協(xié)調(diào)的開(kāi)源服務(wù)器“
1.2 ZooKeeper是個(gè)啥
ZooKeeper 是 Apache 軟件基金會(huì)的一個(gè)軟件項(xiàng)目,它為大型「分布式計(jì)算」提供開(kāi)源的分布式配置服務(wù)、同步服務(wù)和命名注冊(cè)。
Zookeeper 最早起源于雅虎研究院的一個(gè)研究小組。在當(dāng)時(shí),研究人員發(fā)現(xiàn),在雅虎內(nèi)部很多大型系統(tǒng)基本都需要依賴一個(gè)類似的系統(tǒng)來(lái)進(jìn)行分布式協(xié)調(diào),但是這些系統(tǒng)往往都存在分布式單點(diǎn)問(wèn)題。所以,雅虎的開(kāi)發(fā)人員就試圖開(kāi)發(fā)一個(gè)通用的無(wú)單點(diǎn)問(wèn)題的分布式協(xié)調(diào)框架,以便讓開(kāi)發(fā)人員將精力集中在處理業(yè)務(wù)邏輯上,Zookeeper 就這樣誕生了。后來(lái)捐贈(zèng)給了 Apache
,現(xiàn)已成為 Apache
頂級(jí)項(xiàng)目。
關(guān)于“ZooKeeper”這個(gè)項(xiàng)目的名字,其實(shí)也有一段趣聞。在立項(xiàng)初期,考慮到之前內(nèi)部很多項(xiàng)目都是使用動(dòng)物的名字來(lái)命名的(例如著名的Pig項(xiàng)目),雅虎的工程師希望給這個(gè)項(xiàng)目也取一個(gè)動(dòng)物的名字。時(shí)任研究院的首席科學(xué)家 RaghuRamakrishnan 開(kāi)玩笑地說(shuō):“再這樣下去,我們這兒就變成動(dòng)物園了!”此話一出,大家紛紛表示就叫動(dòng)物園管理員吧一一一因?yàn)楦鱾€(gè)以動(dòng)物命名的分布式組件放在一起,雅虎的整個(gè)分布式系統(tǒng)看上去就像一個(gè)大型的動(dòng)物園了,而 Zookeeper 正好要用來(lái)進(jìn)行分布式環(huán)境的協(xié)調(diào)一一于是,Zookeeper 的名字也就由此誕生了。
ZooKeeper 是用于維護(hù)配置信息,命名,提供分布式同步和提供組服務(wù)的集中式服務(wù)。所有這些類型的服務(wù)都以某種形式被分布式應(yīng)用程序使用。每次實(shí)施它們時(shí),都會(huì)進(jìn)行很多工作來(lái)修復(fù)不可避免的 bug 和競(jìng)爭(zhēng)條件。由于難以實(shí)現(xiàn)這類服務(wù),因此應(yīng)用程序最初通常會(huì)跳過(guò)它們,這會(huì)使它們?cè)诖嬖诟牡那闆r下變得脆弱并且難以管理。即使部署正確,這些服務(wù)的不同實(shí)現(xiàn)也會(huì)導(dǎo)致管理復(fù)雜。
ZooKeeper 的目標(biāo)是將這些不同服務(wù)的精華提煉為一個(gè)非常簡(jiǎn)單的接口,用于集中協(xié)調(diào)服務(wù)。服務(wù)本身是分布式的,并且高度可靠。服務(wù)將實(shí)現(xiàn)共識(shí),組管理和狀態(tài)協(xié)議,因此應(yīng)用程序不需要自己實(shí)現(xiàn)它們。
1.3 ZooKeeper工作機(jī)制
ZooKeeper 從設(shè)計(jì)模式角度來(lái)理解:就是一個(gè)基于觀察者模式設(shè)計(jì)的分布式服務(wù)管理框架,它負(fù)責(zé)存儲(chǔ)和管理大家都關(guān)心的數(shù)據(jù),然后接受觀察者的注冊(cè),一旦這些數(shù)據(jù)的狀態(tài)發(fā)生變化,ZK 就將負(fù)責(zé)通知已經(jīng)在 ZK 上注冊(cè)的那些觀察者做出相應(yīng)的反應(yīng),從而實(shí)現(xiàn)集群中類似 Master/Slave 管理模式。
1.4 特性
圖片來(lái)源:官網(wǎng)wikiZooKeeper:一個(gè)領(lǐng)導(dǎo)者(leader),多個(gè)跟隨者(follower)組成的集群。
Leader 負(fù)責(zé)進(jìn)行投票的發(fā)起和決議,更新系統(tǒng)狀態(tài)。
Follower 用于接收客戶請(qǐng)求并向客戶端返回結(jié)果,在選舉 Leader 過(guò)程中參與投票。
集群中只要有半數(shù)以上節(jié)點(diǎn)存活,Zookeeper 集群就能正常服務(wù)。
全局?jǐn)?shù)據(jù)一致(單一視圖):每個(gè) Server 保存一份相同的數(shù)據(jù)副本,Client 無(wú)論連接到哪個(gè) Server,數(shù)據(jù)都是一致的。
順序一致性: 從同一客戶端發(fā)起的事務(wù)請(qǐng)求,最終將會(huì)嚴(yán)格地按照順序被應(yīng)用到 ZooKeeper 中去。
原子性: 所有事務(wù)請(qǐng)求的處理結(jié)果在整個(gè)集群中所有機(jī)器上的應(yīng)用情況是一致的,也就是說(shuō),要么整個(gè)集群中所有的機(jī)器都成功應(yīng)用了某一個(gè)事務(wù),要么都沒(méi)有應(yīng)用。
實(shí)時(shí)性,在一定時(shí)間范圍內(nèi),client 能讀到最新數(shù)據(jù)。
可靠性: 一旦一次更改請(qǐng)求被應(yīng)用,更改的結(jié)果就會(huì)被持久化,直到被下一次更改覆蓋。
1.5 設(shè)計(jì)目標(biāo)
- 簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu) :Zookeeper 使得分布式程序能夠通過(guò)一個(gè)共享的樹(shù)形結(jié)構(gòu)的名字空間來(lái)進(jìn)行相互協(xié)調(diào),即Zookeeper 服務(wù)器內(nèi)存中的數(shù)據(jù)模型由一系列被稱為
ZNode
的數(shù)據(jù)節(jié)點(diǎn)組成,Zookeeper 將全量的數(shù)據(jù)存儲(chǔ)在內(nèi)存中,以此來(lái)提高服務(wù)器吞吐、減少延遲的目的。 - 可以構(gòu)建集群 :Zookeeper 集群通常由一組機(jī)器構(gòu)成,組成 Zookeeper 集群的每臺(tái)機(jī)器都會(huì)在內(nèi)存中維護(hù)當(dāng)前服務(wù)器狀態(tài),并且每臺(tái)機(jī)器之間都相互通信。
- 順序訪問(wèn) :對(duì)于來(lái)自客戶端的每個(gè)更新請(qǐng)求,Zookeeper 都會(huì)分配一個(gè)全局唯一的遞增編號(hào),這個(gè)編號(hào)反映了所有事務(wù)操作的先后順序。
- 高性能 :Zookeeper 和 Redis 一樣全量數(shù)據(jù)存儲(chǔ)在內(nèi)存中,100% 讀請(qǐng)求壓測(cè) QPS 12-13W
1.6 數(shù)據(jù)結(jié)構(gòu)
Zookeeper 數(shù)據(jù)模型的結(jié)構(gòu)與 Unix 文件系統(tǒng)的結(jié)構(gòu)相似,整體上可以看做是一棵樹(shù),每個(gè)節(jié)點(diǎn)稱作一個(gè) 「ZNode」。每個(gè) ZNode 默認(rèn)能存儲(chǔ) 1MB 的數(shù)據(jù),每個(gè) ZNode 都可以通過(guò)其路徑唯一標(biāo)識(shí)。
1.7 應(yīng)用場(chǎng)景
ZooKeeper 是一個(gè)典型的分布式數(shù)據(jù)一致性解決方案,分布式應(yīng)用程序可以基于 ZooKeeper 實(shí)現(xiàn)諸如數(shù)據(jù)發(fā)布/訂閱、負(fù)載均衡、命名服務(wù)、分布式協(xié)調(diào)/通知、集群管理、Master 選舉、分布式鎖和分布式隊(duì)列等功能
統(tǒng)一命名服務(wù)
在分布式系統(tǒng)中,通過(guò)使用命名服務(wù),客戶端應(yīng)用能夠根據(jù)指定名字來(lái)獲取資源或服務(wù)的地址,提供者等信息。被命名的實(shí)體通??梢允羌褐械臋C(jī)器,提供的服務(wù)地址,進(jìn)程對(duì)象等等——這些我們都可以統(tǒng)稱他們?yōu)?strong>名字(Name)。其中較為常見(jiàn)的就是一些分布式服務(wù)框架(如RPC、RMI)中的服務(wù)地址列表。通過(guò)調(diào)用 Zookeeper 提供的創(chuàng)建節(jié)點(diǎn)的 API,能夠很容易創(chuàng)建一個(gè)全局唯一的 path,這個(gè) path 就可以作為一個(gè)名稱。
阿里巴巴開(kāi)源的分布式服務(wù)框架 Dubbo 就使用 ZooKeeper 來(lái)作為其命名服務(wù),維護(hù)全局的服務(wù)地址列表。
數(shù)據(jù)發(fā)布與訂閱(配置中心)
發(fā)布與訂閱模型,即所謂的配置中心,顧名思義就是發(fā)布者將數(shù)據(jù)發(fā)布到 ZooKeeper 節(jié)點(diǎn)上,供訂閱者動(dòng)態(tài)獲取數(shù)據(jù),實(shí)現(xiàn)配置信息的集中式管理和動(dòng)態(tài)更新。例如全局的配置信息,服務(wù)式服務(wù)框架的服務(wù)地址列表等就非常適合使用。
分布式環(huán)境下,配置文件管理和同步是一個(gè)常見(jiàn)問(wèn)題
一個(gè)集群中,所有節(jié)點(diǎn)的配置信息是一致的,比如 Hadoop 集群、集群中的數(shù)據(jù)庫(kù)配置信息等全局配置
對(duì)配置文件修改后,希望能夠快速同步到各個(gè)節(jié)點(diǎn)上。
配置管理可交由 ZooKeeper 實(shí)現(xiàn)
- 可將配置信息寫(xiě)入 ZooKeeper 上的一個(gè) Znode
- 各個(gè)節(jié)點(diǎn)監(jiān)聽(tīng)這個(gè) Znode
- 一旦 Znode 中的數(shù)據(jù)被修改,ZooKeeper 將通知各個(gè)節(jié)點(diǎn)
統(tǒng)一集群管理
所謂集群管理無(wú)在乎兩點(diǎn):是否有機(jī)器退出和加入、選舉 Master。
管理節(jié)點(diǎn)
分布式環(huán)境中,實(shí)時(shí)掌握每個(gè)節(jié)點(diǎn)的狀態(tài)是必要的,比如我們要知道集群中各機(jī)器狀態(tài)、收集各個(gè)機(jī)器的運(yùn)行時(shí)狀態(tài)數(shù)據(jù)、服務(wù)器動(dòng)態(tài)上下線等。
交由 ZooKeeper 實(shí)現(xiàn)的方式
- 可將節(jié)點(diǎn)信息寫(xiě)入 ZooKeeper 上的一個(gè) Znode
- 監(jiān)聽(tīng)這個(gè) Znode 可獲取它的實(shí)時(shí)狀態(tài)變化
- 典型應(yīng)用:HBase 中 Master 狀態(tài)監(jiān)控和選舉。(TODO:圖應(yīng)該是注冊(cè)和Register and watch)
Master選舉
在分布式環(huán)境中,相同的業(yè)務(wù)應(yīng)用分布在不同的機(jī)器上,有些業(yè)務(wù)邏輯(例如一些耗時(shí)的計(jì)算,網(wǎng)絡(luò)I/O處理),往往只需要讓整個(gè)集群中的某一臺(tái)機(jī)器進(jìn)行執(zhí)行,其余機(jī)器可以共享這個(gè)結(jié)果,這樣可以大大減少重復(fù)勞動(dòng),提高性能,于是這個(gè)master選舉便是這種場(chǎng)景下的碰到的主要問(wèn)題。
利用 Zookeeper 的強(qiáng)一致性,能夠很好的保證在分布式高并發(fā)情況下節(jié)點(diǎn)的創(chuàng)建一定是全局唯一的,即:同時(shí)有多個(gè)客戶端請(qǐng)求創(chuàng)建 /currentMaster
節(jié)點(diǎn),最終一定只有一個(gè)客戶端請(qǐng)求能夠創(chuàng)建成功。Zookeeper 通過(guò)這種節(jié)點(diǎn)唯一的特性,可以創(chuàng)建一個(gè) Master 節(jié)點(diǎn),其他客戶端 Watcher 監(jiān)控當(dāng)前 Master 是否存活,一旦 Master 掛了,其他機(jī)器再創(chuàng)建這樣的一個(gè) Master 節(jié)點(diǎn),用來(lái)重新選舉。
軟負(fù)載均衡
分布式系統(tǒng)中,負(fù)載均衡是一種很普遍的技術(shù),為了保證高可用性,通常同一個(gè)應(yīng)用或同一個(gè)服務(wù)的提供方都會(huì)部署多份,達(dá)到對(duì)等服務(wù)??梢允怯布呢?fù)載均衡,如 F5,也可以是軟件的負(fù)載,我們熟知的 Nginx,或者這里介紹的 Zookeeper。
分布式協(xié)調(diào)/通知
Zookeeper 中特有的 「Watcher」 注冊(cè)與異步通知機(jī)制,能夠很好的實(shí)現(xiàn)分布式環(huán)境下不同機(jī)器,甚至不同系統(tǒng)之間的協(xié)調(diào)和通知,從而實(shí)現(xiàn)對(duì)數(shù)據(jù)變更的實(shí)時(shí)處理。
使用方法通常是不同系統(tǒng)都對(duì) ZK 上同一個(gè) znode 進(jìn)行注冊(cè),監(jiān)聽(tīng) znode 的變化(包括 znode 本身內(nèi)容及子節(jié)點(diǎn)的),其中一個(gè)系統(tǒng) update 了 znode,那么另一個(gè)系統(tǒng)能夠收到通知,并作出相應(yīng)處理。
- 心跳檢測(cè)中可以讓檢測(cè)系統(tǒng)和被檢測(cè)系統(tǒng)之間并不直接關(guān)聯(lián)起來(lái),而是通過(guò) ZK 上某個(gè)節(jié)點(diǎn)關(guān)聯(lián),減少系統(tǒng)耦合;
- 系統(tǒng)調(diào)度模式中,假設(shè)某系統(tǒng)有控制臺(tái)和推送系統(tǒng)兩部分組成,控制臺(tái)的職責(zé)是控制推送系統(tǒng)進(jìn)行相應(yīng)的推送工作。管理人員在控制臺(tái)作的一些操作,實(shí)際上是修改了 ZK 上某些節(jié)點(diǎn)的狀態(tài),而 ZK 就把這些變化通知給他們注冊(cè) Watcher 的客戶端,即推送系統(tǒng),于是,作出相應(yīng)的推送任務(wù)。
分布式鎖
分布式鎖,這個(gè)主要得益于 ZooKeeper 為我們保證了數(shù)據(jù)的強(qiáng)一致性。
鎖服務(wù)可以分為兩類,一個(gè)是保持獨(dú)占,另一個(gè)是控制時(shí)序。
所謂保持獨(dú)占,就是所有試圖來(lái)獲取這個(gè)鎖的客戶端,最終只有一個(gè)可以成功獲得這把鎖。通常的做法是把 zk 上的一個(gè) znode 看作是一把鎖,通過(guò) create znode
的方式來(lái)實(shí)現(xiàn)。所有客戶端都去創(chuàng)建 /distribute_lock
節(jié)點(diǎn),最終成功創(chuàng)建的那個(gè)客戶端也即擁有了這把鎖。
控制時(shí)序,就是所有試圖來(lái)獲取這個(gè)鎖的客戶端,最終都是會(huì)被安排執(zhí)行,只是有個(gè)全局時(shí)序了。做法和上面基本類似,只是這里 /distribute_lock
已預(yù)先存在,客戶端在它下面創(chuàng)建臨時(shí)有序節(jié)點(diǎn)(這個(gè)可以通過(guò)節(jié)點(diǎn)的屬性控制:CreateMode.EPHEMERAL_SEQUENTIAL
來(lái)指定)。ZK 的父節(jié)點(diǎn)(/distribute_lock
)維持一份 sequence,保證子節(jié)點(diǎn)創(chuàng)建的時(shí)序性,從而也形成了每個(gè)客戶端的全局時(shí)序。
個(gè)人感覺(jué)還是用 Redis 實(shí)現(xiàn)分布式鎖更加方便。
PS:阿里中間件團(tuán)隊(duì):“其實(shí),ZK 并非天生就是為這些應(yīng)用場(chǎng)景設(shè)計(jì)的,都是后來(lái)眾多開(kāi)發(fā)者根據(jù)其框架的特性,利用其提供的一系列API接口(或者稱為原語(yǔ)集),摸索出來(lái)的典型使用方法?!?/p>
2. Hello ZooKeeper
ZooKeeper 的三種部署方式:
- 單機(jī)模式,即部署在單臺(tái)機(jī)器上的一個(gè) ZK 服務(wù),適用于學(xué)習(xí)、了解 ZK 基礎(chǔ)功能
- 偽分布模式,即部署在一臺(tái)機(jī)器上的多個(gè)(原則上大于3個(gè))ZK 服務(wù),偽集群,適用于學(xué)習(xí)、開(kāi)發(fā)和測(cè)試
- 全分布式模式(復(fù)制模式),即在多臺(tái)機(jī)器上部署服務(wù),真正的集群模式,生產(chǎn)環(huán)境中使用
計(jì)劃寫(xiě)三篇的,第二篇會(huì)實(shí)戰(zhàn) coding,運(yùn)用各種 API,到時(shí)候再裝集群,本節(jié)先來(lái)個(gè)單機(jī)玩~~
2.1 本地模式安裝部署
2.1.1 安裝前準(zhǔn)備
拷貝或下載 Zookeeper 安裝包到 Linux 系統(tǒng)下(這里有個(gè)小問(wèn)題,如果你下載 ZK 版本是3.5+ 的話,要下載 bin.tar.gz,愚笨的我最先沒(méi)看到官網(wǎng)說(shuō)明,一頓操作各種報(bào)錯(cuò)找不到 Main 方法)
解壓到指定目錄
tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz
2.1.2 配置修改
將 zookeeper-3.5.7/conf 這個(gè)路徑下的 zoo_sample.cfg
修改為 zoo.cfg
;
mv zoo_sample.cfg zoo.cfg
打開(kāi) zoo.cfg 文件,修改 dataDir 路徑:
dataDir=XXX/zookeeper-3.5.7/zkData
2.1.3 操作 Zookeeper
- 啟動(dòng) Zookeeper:
bin/zkServer.sh start
/usr/local/bin/java
ZooKeeper JMX enabled by default
Using config: /home/sync360/test/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
4020 Jps
4001 QuorumPeerMain
- 查看狀態(tài):
bin/zkServer.sh status
/usr/local/bin/java
ZooKeeper JMX enabled by default
Using config: /home/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: standalone
Connecting to localhost:2181
2020-03-25 15:41:19,112 [myid:] - INFO [main:Environment@109] - Client environment:zookeeper.version=3.5.7-f0fdd52973d373ffd9c86b81d99842dc2c7f660e, built on 02/10/2020 11:30 GMT
...
2020-03-25 15:41:19,183 [myid:] - INFO [main:ClientCnxn@1653] - zookeeper.request.timeout value is 0. feature enabled=
Welcome to ZooKeeper!
...
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
停止 Zookeeper: bin/zkServer.sh stop
2.2 常用命令
命令基本語(yǔ)法 | 功能描述 |
---|
help | 顯示所有操作命令 |
ls path [watch] | 使用 ls 命令來(lái)查看當(dāng)前znode中所包含的內(nèi)容 |
ls2 path [watch] | 查看當(dāng)前節(jié)點(diǎn)數(shù)據(jù)并能看到更新次數(shù)等數(shù)據(jù) |
create | 普通創(chuàng)建-s 含有序列-e 臨時(shí)(重啟或者超時(shí)消失) |
get path [watch] | 獲得節(jié)點(diǎn)的值 |
set | 設(shè)置節(jié)點(diǎn)的具體值 |
stat | 查看節(jié)點(diǎn)狀態(tài) |
delete | 刪除節(jié)點(diǎn) |
rmr | 遞歸刪除節(jié)點(diǎn) |
ls 查看當(dāng)前 zk 中所包含的內(nèi)容
[zk: localhost:2181(CONNECTED) 1] ls /
[lazyegg, zookeeper]
create 創(chuàng)建一個(gè)新的 znode
[zk: localhost:2181(CONNECTED) 2] create /test
Created /test
get 查看新的 znode 的值
[zk: localhost:2181(CONNECTED) 4] get /test
null
可以看到值為 null,我們剛才設(shè)置了一個(gè)沒(méi)有值的節(jié)點(diǎn),也可以通過(guò) create /zoo dog
直接創(chuàng)建有內(nèi)容的節(jié)點(diǎn)
set 對(duì) zk 所關(guān)聯(lián)的字符串進(jìn)行設(shè)置
set /test hello
delete 刪除節(jié)點(diǎn)
delete /test
2.3 配置參數(shù)解讀
在 Zookeeper 的設(shè)計(jì)中,如果是集群模式,那所有機(jī)器上的 zoo.cfg 文件內(nèi)容應(yīng)該都是一致的。
Zookeeper 中的配置文件 zoo.cfg
中參數(shù)含義解讀如下:
tickTime =2000:通信心跳數(shù)
Zookeeper 使用的基本時(shí)間,服務(wù)器之間或客戶端與服務(wù)器之間維持心跳的時(shí)間間隔,也就是每個(gè) tickTime時(shí)間就會(huì)發(fā)送一個(gè)心跳,時(shí)間單位為毫秒
它用于心跳機(jī)制,并且設(shè)置最小的 session 超時(shí)時(shí)間為兩倍心跳時(shí)間。(session的最小超時(shí)時(shí)間是2*tickTime);
initLimit =10:主從初始通信時(shí)限,集群中的 Follower 跟隨者服務(wù)器與 Leader 領(lǐng)導(dǎo)者服務(wù)器之間初始連接時(shí)能容忍的最多心跳數(shù)(tickTime的數(shù)量),用它來(lái)限定集群中的 ZK 服務(wù)器連接到 Leader 的時(shí)限;
syncLimit =5:主從同步通信時(shí)限,集群中 Leader 與 Follower 之間的最大響應(yīng)時(shí)間單位,假如響應(yīng)超過(guò)syncLimit * tickTime
,Leader 認(rèn)為 Follwer 死掉,從服務(wù)器列表中刪除 Follwer;
dataDir:數(shù)據(jù)文件目錄+數(shù)據(jù)持久化路徑;
3. 你要知道的概念
- ZooKeeper 本身就是一個(gè)分布式程序(只要半數(shù)以上節(jié)點(diǎn)存活,ZooKeeper 就能正常服務(wù))。
- 為了保證高可用,最好是以集群形態(tài)來(lái)部署 ZooKeeper,這樣只要集群中大部分機(jī)器是可用的(能夠容忍一定的機(jī)器故障),那么 ZooKeeper 本身仍然是可用的。
- ZooKeeper 將數(shù)據(jù)保存在內(nèi)存中,這也就保證了高吞吐量和低延遲(但是內(nèi)存限制了能夠存儲(chǔ)的容量不太大,此限制也是保持 znode 中存儲(chǔ)的數(shù)據(jù)量較小的進(jìn)一步原因)。
- ZooKeeper 是高性能的。在“讀”多于“寫(xiě)”的應(yīng)用程序中尤其的高性能,因?yàn)椤皩?xiě)”會(huì)導(dǎo)致所有的服務(wù)器間同步狀態(tài)。(“讀”多于“寫(xiě)”是協(xié)調(diào)服務(wù)的典型場(chǎng)景。)
- ZooKeeper 底層其實(shí)只提供了兩個(gè)功能:
- 管理(存儲(chǔ)、讀取)用戶程序提交的數(shù)據(jù)
- 為用戶程序提交數(shù)據(jù)節(jié)點(diǎn)監(jiān)聽(tīng)服務(wù)
這里引入一個(gè)簡(jiǎn)單的例子,逐個(gè)介紹一些 ZK 中的概念。
在分布式系統(tǒng)中經(jīng)常會(huì)遇到這種情況,多個(gè)應(yīng)用讀取同一個(gè)配置。例如:Client1,Client2 兩個(gè)應(yīng)用都會(huì)讀取配置 B 中的內(nèi)容,一旦 B 中的內(nèi)容出現(xiàn)變化,就會(huì)通知 Client1 和 Client2。
一般的做法是在 Client1,Client2 中按照時(shí)鐘頻率詢問(wèn) B 的變化,或者使用觀察者模式來(lái)監(jiān)聽(tīng) B 的變化,發(fā)現(xiàn)變化以后再更新兩個(gè)客戶端。那么 ZooKeeper 如何協(xié)調(diào)這種場(chǎng)景?
這兩個(gè)客戶端連接到 ZooKeeper 的服務(wù)器,并獲取其中存放的 B。保存 B 值的地方在 ZooKeeper 服務(wù)端中就稱為 ZNode。
3.1 數(shù)據(jù)節(jié)點(diǎn)(Znode)
在談到分布式的時(shí)候,我們通常說(shuō)的“節(jié)點(diǎn)'是指組成集群的每一臺(tái)機(jī)器。然而,在 Zookeeper 中,“節(jié)點(diǎn)'分為兩類,第一類同樣是指構(gòu)成集群的機(jī)器,我們稱之為「機(jī)器節(jié)點(diǎn)」;第二類則是指數(shù)據(jù)模型中的數(shù)據(jù)單元,我們稱之為「數(shù)據(jù)節(jié)點(diǎn)」一一ZNode。上圖中的 A、B 就是一個(gè)數(shù)據(jù)結(jié)點(diǎn)。
Zookeeper 將所有數(shù)據(jù)存儲(chǔ)在內(nèi)存中,數(shù)據(jù)模型是一棵樹(shù)(Znode Tree),由斜杠(/)進(jìn)行分割的路徑,就是一個(gè) Znode,例如 /Configuration/B
。每個(gè) Znode 上都會(huì)保存自己的數(shù)據(jù)內(nèi)容,同時(shí)還會(huì)保存一系列屬性信息。
在 Zookeeper 中,Znode 可以分為持久節(jié)點(diǎn)和臨時(shí)節(jié)點(diǎn)兩類。
- 所謂持久節(jié)點(diǎn)是指一旦這個(gè) ZNode 被創(chuàng)建了,除非主動(dòng)進(jìn)行 ZNode 的移除操作,否則這個(gè) ZNode 將一直保存在 Zookeeper 上。
- 而臨時(shí)節(jié)點(diǎn)就不一樣了,它的生命周期和客戶端會(huì)話綁定,一旦客戶端會(huì)話失效,那么這個(gè)客戶端創(chuàng)建的所有臨時(shí)節(jié)點(diǎn)都會(huì)被移除。
另外,ZooKeeper 還允許用戶為每個(gè)節(jié)點(diǎn)添加一個(gè)特殊的屬性:**SEQUENTIAL。**也被叫做 順序結(jié)點(diǎn),一旦節(jié)點(diǎn)被標(biāo)記上這個(gè)屬性,那么在這個(gè)節(jié)點(diǎn)被創(chuàng)建的時(shí)候,Zookeeper 會(huì)自動(dòng)在其節(jié)點(diǎn)名后面追加上一個(gè)整型數(shù)字,這個(gè)整型數(shù)字是一個(gè)由父節(jié)點(diǎn)維護(hù)的自增數(shù)字。
3.2 事件監(jiān)聽(tīng)器(Watcher)
上面說(shuō)了 ZooKeeper 用來(lái)存放數(shù)據(jù)的 ZNode,并且把 B 的值存儲(chǔ)在里面。如果 B 被更新了,兩個(gè)客戶端(Client1、Client2)如何獲得通知呢?
Zookeeper 允許用戶在指定節(jié)點(diǎn)上注冊(cè)一些 Watcher,當(dāng) Znode 發(fā)生變化時(shí),將觸發(fā)并刪除一個(gè) watch。當(dāng) watch 被觸發(fā)時(shí)客戶端會(huì)收到一個(gè)數(shù)據(jù)包,指示 znode 已經(jīng)被修改。如果客戶端和 ZooKeeper 服務(wù)器之間的連接中斷,客戶端將收到本地通知。該機(jī)制是 Zookeeper 實(shí)現(xiàn)分布式協(xié)調(diào)服務(wù)的重要特性。
3.6.0中的新增功能:客戶端還可以在 znode 上設(shè)置永久性的遞歸監(jiān)視,這些監(jiān)視在觸發(fā)時(shí)不會(huì)刪除,并且會(huì)以遞歸方式觸發(fā)已注冊(cè) znode 以及所有子 znode 的更改。
ZooKeeper 客戶端(Client)會(huì)在指定的節(jié)點(diǎn)(/Configuration/B)上注冊(cè)一個(gè) Watcher,ZNode 上的 B 被更新的時(shí)候,服務(wù)端就會(huì)通知 Client1 和 Client2。
3.3 版本
有了 Watcher 機(jī)制,就可以實(shí)現(xiàn)分布式協(xié)調(diào)/通知了,假設(shè)有這樣的場(chǎng)景,兩個(gè)客戶端同時(shí)對(duì) B 進(jìn)行寫(xiě)入操作,這兩個(gè)客戶端就會(huì)存在競(jìng)爭(zhēng)關(guān)系,通常需要對(duì) B 進(jìn)行加鎖操作,ZK 通過(guò) version 版本號(hào)來(lái)控制實(shí)現(xiàn)樂(lè)觀鎖中的“寫(xiě)入校驗(yàn)”機(jī)制。
Zookeeper 的每個(gè) ZNode 上都會(huì)存儲(chǔ)數(shù)據(jù),對(duì)應(yīng)于每個(gè) ZNode,Zookeeper 都會(huì)為其維護(hù)一個(gè)叫作 Stat 的數(shù)據(jù)結(jié)構(gòu),Stat 中記錄了這個(gè) ZNode 的三個(gè)數(shù)據(jù)版本,分別是 version(當(dāng)前ZNode的版本)、cversion(當(dāng)前ZNode 子節(jié)點(diǎn)的版本)和 aversion(當(dāng)前ZNode的ACL版本)。
znode 里都有些啥呢?
3.4 Stat 結(jié)構(gòu)體
Znodes 維護(hù)了一個(gè) stat 結(jié)構(gòu),其中包含數(shù)據(jù)更改、ACL更改的版本號(hào)、時(shí)間戳等。
狀態(tài)屬性 | 說(shuō)明 |
---|
czxid | 創(chuàng)建節(jié)點(diǎn)的事務(wù)zxid。每次修改 ZK 狀態(tài)都會(huì)收到一個(gè)zxid形式的時(shí)間戳,也就是 ZK 事務(wù)ID。事務(wù)ID是 ZK 中所有修改總的次序。每個(gè)修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前發(fā)生 |
ctime | znode被創(chuàng)建的毫秒數(shù)(從1970年開(kāi)始) |
mzxid | znode最后更新的事務(wù)zxid |
mtime | znode最后修改的毫秒數(shù)(從1970年開(kāi)始) |
pzxid | znode最后更新的子節(jié)點(diǎn)zxid |
version | 數(shù)據(jù)節(jié)點(diǎn)版本號(hào) |
cversion | 子節(jié)點(diǎn)版本號(hào),znode子節(jié)點(diǎn)修改次數(shù) |
aversion | znode訪問(wèn)控制列表的變化號(hào) |
ephemeralOwner | 如果是臨時(shí)節(jié)點(diǎn),這個(gè)是znode擁有者的session id。如果不是臨時(shí)節(jié)點(diǎn)則是0 |
dataLength | znode的數(shù)據(jù)長(zhǎng)度 |
numChildren | znode子節(jié)點(diǎn)數(shù)量 |
3.5 會(huì)話(Session)
Session 指的是 ZooKeeper 服務(wù)器與客戶端會(huì)話。
在 ZooKeeper 中,一個(gè)客戶端連接是指客戶端和服務(wù)器之間的一個(gè) TCP 長(zhǎng)連接??蛻舳藛?dòng)的時(shí)候,首先會(huì)與服務(wù)器建立一個(gè) TCP 連接,從第一次連接建立開(kāi)始,客戶端會(huì)話的生命周期也開(kāi)始了。通過(guò)這個(gè)連接,客戶端能夠通過(guò)心跳檢測(cè)與服務(wù)器保持有效的會(huì)話,也能夠向 Zookeeper 服務(wù)器發(fā)送請(qǐng)求并接受響應(yīng),同時(shí)還能夠通過(guò)該連接接收來(lái)自服務(wù)器的 Watch 事件通知。
Session 作為會(huì)話實(shí)體,用來(lái)代表客戶端會(huì)話,其包括 4 個(gè)屬性:
- SessionID,用來(lái)全局唯一識(shí)別會(huì)話;
- TimeOut,會(huì)話超時(shí)事件??蛻舳嗽趧?chuàng)造 Session 實(shí)例的時(shí)候,會(huì)設(shè)置一個(gè)會(huì)話超時(shí)的時(shí)間。當(dāng)由于服務(wù)器壓力太大、網(wǎng)絡(luò)故障或是客戶端主動(dòng)斷開(kāi)連接等各種原因?qū)е驴蛻舳诉B接斷開(kāi)時(shí),只要在 sessionTimeout 規(guī)定的時(shí)間內(nèi)能夠重新連接上集群中任意一臺(tái)服務(wù)器,那么之前創(chuàng)建的會(huì)話仍然有效;
- TickTime,下次會(huì)話超時(shí)時(shí)間點(diǎn);
- isClosing,當(dāng)服務(wù)端如果檢測(cè)到會(huì)話超時(shí)失效了,會(huì)通過(guò)設(shè)置這個(gè)屬性將會(huì)話關(guān)閉。
3.6 ACL
Zookeeper 采用 ACL(Access Control Lists)策略來(lái)進(jìn)行權(quán)限控制,類似于 UNIX 文件系統(tǒng)的權(quán)限控制。Zookeeper 定義了如下 5 種權(quán)限:
- CREATE: 創(chuàng)建子節(jié)點(diǎn)的權(quán)限
- READ: 獲取節(jié)點(diǎn)數(shù)據(jù)和子節(jié)點(diǎn)列表的權(quán)限
- WRITE: 更新節(jié)點(diǎn)數(shù)據(jù)的權(quán)限
- DELETE: 刪除子節(jié)點(diǎn)的權(quán)限
- ADMIN: 設(shè)置節(jié)點(diǎn)ACL的權(quán)限
其中尤其需要注意的是,CREATE 和 DELETE 這兩種權(quán)限都是針對(duì)子節(jié)點(diǎn)的權(quán)限控制。
3.7 集群角色
最典型集群模式:Master/Slave 模式(主備模式)。在這種模式中,通常 Master 服務(wù)器作為主服務(wù)器提供寫(xiě)服務(wù),其他的 Slave 從服務(wù)器通過(guò)異步復(fù)制的方式獲取 Master 服務(wù)器最新的數(shù)據(jù)提供讀服務(wù)。
但是,在 ZooKeeper 中沒(méi)有選擇傳統(tǒng)的 Master/Slave 概念,而是引入了Leader、Follower 和 Observer 三種角色。
- Leader:為客戶端提供讀和寫(xiě)的服務(wù),負(fù)責(zé)投票的發(fā)起和決議,更新系統(tǒng)狀態(tài)
- Follower:為客戶端提供讀服務(wù),如果是寫(xiě)服務(wù)則轉(zhuǎn)發(fā)給 Leader。在選舉過(guò)程中參與投票
- Observer:為客戶端提供讀服務(wù)器,如果是寫(xiě)服務(wù)則轉(zhuǎn)發(fā)給 Leader。不參與選舉過(guò)程中的投票,也不參與“過(guò)半寫(xiě)成功”策略。在不影響寫(xiě)性能的情況下提升集群的讀性能。此角色是在 zookeeper3.3 系列新增的角色。
server 狀態(tài)
- LEADING:領(lǐng)導(dǎo)者狀態(tài),表明當(dāng)前服務(wù)器角色是 Leader
- FOLLOWING:跟隨者狀態(tài),表明當(dāng)前服務(wù)器角色是 Follower
- OBSERVING:觀察者狀態(tài),表明當(dāng)前服務(wù)器角色是 Observer
選舉機(jī)制
zk-vote- 服務(wù)器1啟動(dòng),此時(shí)只有它一臺(tái)服務(wù)器啟動(dòng)了,它發(fā)出去的報(bào)文沒(méi)有任何響應(yīng),所以它的選舉狀態(tài)一直是LOOKING 狀態(tài)。
- 服務(wù)器2啟動(dòng),它與最開(kāi)始啟動(dòng)的服務(wù)器1進(jìn)行通信,互相交換自己的選舉結(jié)果,由于兩者都沒(méi)有歷史數(shù)據(jù),所以 id 值較大的服務(wù)器2勝出,但是由于沒(méi)有達(dá)到超過(guò)半數(shù)以上的服務(wù)器都同意選舉它(這個(gè)例子中的半數(shù)以上是3),所以服務(wù)器1、2還是繼續(xù)保持 LOOKING 狀態(tài)。
- 服務(wù)器3啟動(dòng),根據(jù)前面的理論分析,服務(wù)器3成為服務(wù)器1、2、3中的老大,而與上面不同的是,此時(shí)有三臺(tái)服務(wù)器選舉了它,所以它成為了這次選舉的Leader。
- 服務(wù)器4啟動(dòng),根據(jù)前面的分析,理論上服務(wù)器4應(yīng)該是服務(wù)器1、2、3、4中最大的,但是由于前面已經(jīng)有半數(shù)以上的服務(wù)器選舉了服務(wù)器3,所以它只能接受當(dāng)小弟的命了。
- 服務(wù)器5啟動(dòng),同4一樣當(dāng)小弟。
Watcher 監(jiān)聽(tīng)器
Zookeeper 中最有特色且最不容易理解的是監(jiān)視(Watches)。
Zookeeper 所有的讀操作——getData(),getChildren(), 和 exists() 都可以設(shè)置監(jiān)視(watch),監(jiān)視事件可以理解為一次性的觸發(fā)器, 官方定義如下:a watch event is one-time trigger, sent to the client that set the watch, which occurs when the data for which the watch was set changes。對(duì)此需要作出如下理解:
One-time trigger(一次性觸發(fā))
當(dāng)設(shè)置監(jiān)視的數(shù)據(jù)發(fā)生改變時(shí),該監(jiān)視事件會(huì)被發(fā)送到客戶端,例如,如果客戶端調(diào)用了 getData('/znode1', true)
并且稍后 /znode1
節(jié)點(diǎn)上的數(shù)據(jù)發(fā)生了改變或者被刪除了,客戶端將會(huì)獲取到 /znode1
發(fā)生變化的監(jiān)視事件,而如果 /znode1
再一次發(fā)生了變化,除非客戶端再次對(duì) /znode1
設(shè)置監(jiān)視,否則客戶端不會(huì)收到事件通知。(3.6之后可以設(shè)置永久監(jiān)視)
Sent to the client(發(fā)送至客戶端)
Zookeeper 客戶端和服務(wù)端是通過(guò) socket 進(jìn)行通信的,由于網(wǎng)絡(luò)存在故障,所以監(jiān)視事件很有可能不會(huì)成功到達(dá)客戶端,監(jiān)視事件是異步發(fā)送至監(jiān)視者的,Zookeeper 本身提供了保序性(ordering guarantee):即客戶端只有首先看到了監(jiān)視事件后,才會(huì)感知到它所設(shè)置監(jiān)視的 znode 發(fā)生了變化(a client will never see a change for which it has set a watch until it first sees the watch event)。網(wǎng)絡(luò)延遲或者其他因素可能導(dǎo)致不同的客戶端在不同的時(shí)刻感知某一監(jiān)視事件,但是不同的客戶端所看到的一切具有一致的順序。
The data for which the watch was set(被設(shè)置 watch 的數(shù)據(jù))
這意味著 znode 節(jié)點(diǎn)本身具有不同的改變方式。你也可以想象 Zookeeper 維護(hù)了兩條監(jiān)視鏈表:數(shù)據(jù)監(jiān)視和子節(jié)點(diǎn)監(jiān)視(data watches and child watches), getData()
和 exists()
設(shè)置數(shù)據(jù)監(jiān)視,getChildren()
設(shè)置子節(jié)點(diǎn)監(jiān)視?;蛘?,你也可以想象 Zookeeper 設(shè)置的不同監(jiān)視返回不同的數(shù)據(jù),getData()
和 exists()
返回 znode 節(jié)點(diǎn)的相關(guān)信息,而 getChildren()
返回子節(jié)點(diǎn)列表。因此, setData()
會(huì)觸發(fā)設(shè)置在某一節(jié)點(diǎn)上所設(shè)置的數(shù)據(jù)監(jiān)視(假定數(shù)據(jù)設(shè)置成功),而一次成功的 create()
操作則會(huì)觸發(fā)當(dāng)前節(jié)點(diǎn)上所設(shè)置的數(shù)據(jù)監(jiān)視以及父節(jié)點(diǎn)的子節(jié)點(diǎn)監(jiān)視。一次成功的 delete()
操作將會(huì)觸發(fā)當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)監(jiān)視和子節(jié)點(diǎn)監(jiān)視事件,同時(shí)也會(huì)觸發(fā)該節(jié)點(diǎn)父節(jié)點(diǎn)的 child watch
。
Zookeeper 中的監(jiān)視是輕量級(jí)的,因此容易設(shè)置、維護(hù)和分發(fā)。當(dāng)客戶端與 Zookeeper 服務(wù)器端失去聯(lián)系時(shí),客戶端并不會(huì)收到監(jiān)視事件的通知,只有當(dāng)客戶端重新連接后,若在必要的情況下,以前注冊(cè)的監(jiān)視會(huì)重新被注冊(cè)并觸發(fā),對(duì)于開(kāi)發(fā)人員來(lái)說(shuō)這通常是透明的。只有一種情況會(huì)導(dǎo)致監(jiān)視事件的丟失,即:通過(guò) exists()
設(shè)置了某個(gè) znode 節(jié)點(diǎn)的監(jiān)視,但是如果某個(gè)客戶端在此 znode 節(jié)點(diǎn)被創(chuàng)建和刪除的時(shí)間間隔內(nèi)與 zookeeper 服務(wù)器失去了聯(lián)系,該客戶端即使稍后重新連接 zookeepe r服務(wù)器后也得不到事件通知。
圖片來(lái)源:yht7從上圖可以看到,Watcher 機(jī)制包括三個(gè)角色:客戶端線程、客戶端的 WatchManager 以及 ZooKeeper 服務(wù)器。Watcher 機(jī)制就是這三個(gè)角色之間的交互,整個(gè)過(guò)程分為注冊(cè)、存儲(chǔ)和通知三個(gè)步驟:
- 客戶端向 ZooKeeper 服務(wù)器注冊(cè)一個(gè) Watcher 監(jiān)聽(tīng);
- 把這個(gè)監(jiān)聽(tīng)信息存儲(chǔ)到客戶端的 WatchManager 中;
- 當(dāng) ZooKeeper 中的節(jié)點(diǎn)發(fā)生變化時(shí),會(huì)通知客戶端,客戶端會(huì)調(diào)用相應(yīng) Watcher 對(duì)象中的回調(diào)方法。
也不知道有木有人對(duì)下一篇的實(shí)戰(zhàn)環(huán)節(jié)感興趣~~~~~
參考:
《從Paxos到ZooKeeper 分布式一致性原理與實(shí)踐》
《阿里中間件團(tuán)隊(duì)博客》http://jm./2011/10/08/1232/
《Zookeeper官方文檔》https://zookeeper./doc/
《尚硅谷Zookeeper》
https://cloud.tencent.com/developer/article/1578401