10年架構(gòu)師經(jīng)典總結(jié):Zookeeper學(xué)習(xí)之原理
一、zookeeper 是什么
Zookeeper是一個(gè)分布式協(xié)調(diào)服務(wù),可用于服務(wù)發(fā)現(xiàn),分布式鎖,分布式領(lǐng)導(dǎo)選舉,配置管理等。這一切的基礎(chǔ),都是Zookeeper提供了一個(gè)類似于Linux文件系統(tǒng)的樹形結(jié)構(gòu)(可認(rèn)為是輕量級(jí)的內(nèi)存文件系統(tǒng),但只適合存少量信息,完全不適合存儲(chǔ)大量文件或者大文件),同時(shí)提供了對(duì)于每個(gè)節(jié)點(diǎn)的監(jiān)控與通知機(jī)制。既然是一個(gè)文件系統(tǒng),就不得不提Zookeeper是如何保證數(shù)據(jù)的一致性的。
二、zookeeper 集群架構(gòu)

Zookeeper集群是一個(gè)基于主從復(fù)制的高可用集群,通常 Master服務(wù)器作為主服務(wù)器提供寫服務(wù),其他的 Slave 服務(wù)器通過異步復(fù)制的方式獲取 Master 服務(wù)器最新的數(shù)據(jù),并提供讀服務(wù),在 ZooKeeper 中沒有選擇傳統(tǒng)的 Master/Slave 概念,而是引入了Leader、Follower 和 Observer 三種角色,每個(gè)角色承擔(dān)如下:
- Leader 一個(gè)Zookeeper集群同一時(shí)間只會(huì)有一個(gè)實(shí)際工作的Leader,它會(huì)發(fā)起并維護(hù)與各Follwer及Observer間的心跳。所有的寫操作必須要通過Leader完成再由Leader將寫操作廣播給其它服務(wù)器。
- Follower 一個(gè)Zookeeper集群可能同時(shí)存在多個(gè)Follower,它會(huì)響應(yīng)Leader的心跳。Follower可直接處理并返回客戶端的讀請(qǐng)求,同時(shí)會(huì)將寫請(qǐng)求轉(zhuǎn)發(fā)給Leader處理,并且負(fù)責(zé)在Leader處理寫請(qǐng)求時(shí),對(duì)請(qǐng)求進(jìn)行投票(“過半寫成功”策略)。
- Observer 角色與Follower類似,但是無投票權(quán)。
在集群中zookeeper是如何保證master與slave數(shù)據(jù)一致性?
為了保證寫操作的一致性與可用性,Zookeeper專門設(shè)計(jì)了一種名為原子廣播(ZAB)的支持崩潰恢復(fù)的一致性協(xié)議?;谠搮f(xié)議,Zookeeper實(shí)現(xiàn)了一種主從模式的系統(tǒng)架構(gòu)來保持集群中各個(gè)副本之間的數(shù)據(jù)一致性。
寫數(shù)據(jù)時(shí)保證一致性:Zookeeper 客戶端會(huì)隨機(jī)連接到 Zookeeper 集群的一個(gè)節(jié)點(diǎn),如果是讀請(qǐng)求,就直接從當(dāng)前節(jié)點(diǎn)中讀取數(shù)據(jù);如果是寫請(qǐng)求且當(dāng)前節(jié)點(diǎn)不是leader,那么節(jié)點(diǎn)就會(huì)向 leader 提交事務(wù),leader 會(huì)廣播事務(wù),只要有超過半數(shù)節(jié)點(diǎn)寫入成功,該寫請(qǐng)求就會(huì)被提交(類 2PC 協(xié)議)。
服務(wù)器運(yùn)行時(shí)期的Leader選舉(當(dāng)leader當(dāng)機(jī)后如何選主)?
zookeeper 在集群模式下,leader宕機(jī)也不會(huì)影響繼續(xù)提供服務(wù),但是leader宕機(jī)在從新選主過程時(shí)無法對(duì)外提供服務(wù),會(huì)有一個(gè)短暫的停頓過程(這里就是與eureka的區(qū)別)。
- 集群已存在leader現(xiàn)在又假如一臺(tái)服務(wù)器:對(duì)于集群中已經(jīng)存在Leader而言,此種情況一般都是某臺(tái)機(jī)器啟動(dòng)得較晚,在其啟動(dòng)之前,集群已經(jīng)在正常工作,對(duì)這種情況,該機(jī)器試圖去選舉Leader時(shí),會(huì)被告知當(dāng)前服務(wù)器的Leader信息,對(duì)于該機(jī)器而言,僅僅需要和Leader機(jī)器建立起連接,并進(jìn)行狀態(tài)同步即可。
- 集群存在leader宕機(jī)需要重新選舉leader:例如server3 宕機(jī)了。則剩余的 每個(gè)Server發(fā)出一個(gè)投票。Server1和Server2都會(huì)將自己作為L(zhǎng)eader服務(wù)器來進(jìn)行投票,每次投票會(huì)包含所推舉的服務(wù)器的myid和ZXID,使用(myid, ZXID)來表示,此時(shí)Server1的投票為(1, 0),Server2的投票為(2, 0),然后各自將這個(gè)投票發(fā)給集群中其他機(jī)器。當(dāng)新的leader選擇出來以后,第二步就是數(shù)據(jù)同步保證所有的節(jié)點(diǎn)與leader數(shù)據(jù)一致。
處理投票。針對(duì)每一個(gè)投票,服務(wù)器都需要將別人的投票和自己的投票進(jìn)行PK,PK規(guī)則如下
- 優(yōu)先檢查ZXID。ZXID比較大的服務(wù)器優(yōu)先作為L(zhǎng)eader。
- 如果ZXID相同,那么就比較myid。myid較大的服務(wù)器作為L(zhǎng)eader服務(wù)器。
為什么最好使用奇數(shù)臺(tái)服務(wù)器構(gòu)成 ZooKeeper 集群?
zookeeper有這樣一個(gè)特性:集群中只要有過半的機(jī)器是正常工作的,那么整個(gè)集群對(duì)外就是可用的。也就是說如果有2個(gè)zookeeper,那么只要有1個(gè)死了zookeeper就不能用了,因?yàn)?沒有過半,所以2個(gè)zookeeper的死亡容忍度為0;同理,要是有3個(gè)zookeeper,一個(gè)死了,還剩下2個(gè)正常的,過半了(2>3/2),所以3個(gè)zookeeper的容忍度為1。如果是4臺(tái)zookeeper 如果掛掉2臺(tái), 還剩下2臺(tái) (2 不大于 4/2),顯然不過半所以集群還是不可用,4臺(tái)的容忍度還是1。因此不是 不能部署偶數(shù)臺(tái),而是偶數(shù)臺(tái)對(duì)于高可用作用不大,浪費(fèi)服務(wù)器。
三、ZooKeeper 的一些重要概念
ZooKeeper 將數(shù)據(jù)保存在內(nèi)存中,這也就保證了 高吞吐量和低延遲(但是內(nèi)存限制了能夠存儲(chǔ)的容量不太大,此限制也是保持znode中存儲(chǔ)的數(shù)據(jù)量較小的進(jìn)一步原因)。
ZooKeeper 是高性能的。 在“讀”多于“寫”的應(yīng)用程序中尤其地高性能,因?yàn)?ldquo;寫”會(huì)導(dǎo)致所有的服務(wù)器間同步狀態(tài)。(“讀”多于“寫”是協(xié)調(diào)服務(wù)的典型場(chǎng)景。)
會(huì)話(Session)
Session 指的是 ZooKeeper 服務(wù)器與客戶端會(huì)話。在 ZooKeeper 中,一個(gè)客戶端連接是指客戶端和服務(wù)器之間的一個(gè) TCP 長(zhǎng)連接??蛻舳藛?dòng)的時(shí)候,首先會(huì)與服務(wù)器建立一個(gè) TCP 連接,從第一次連接建立開始,客戶端會(huì)話的生命周期也開始了。通過這個(gè)連接,客戶端能夠通過心跳檢測(cè)與服務(wù)器保持有效的會(huì)話,也能夠向Zookeeper服務(wù)器發(fā)送請(qǐng)求并接受響應(yīng),同時(shí)還能夠通過該連接接收來自服務(wù)器的Watch事件通知。 Session的sessionTimeout值用來設(shè)置一個(gè)客戶端會(huì)話的超時(shí)時(shí)間。當(dāng)由于服務(wù)器壓力太大、網(wǎng)絡(luò)故障或是客戶端主動(dòng)斷開連接等各種原因?qū)е驴蛻舳诉B接斷開時(shí),只要在sessionTimeout規(guī)定的時(shí)間內(nèi)能夠重新連接上集群中任意一臺(tái)服務(wù)器,那么之前創(chuàng)建的會(huì)話仍然有效。
在為客戶端創(chuàng)建會(huì)話之前,服務(wù)端首先會(huì)為每個(gè)客戶端都分配一個(gè)sessionID。由于 sessionID 是 Zookeeper 會(huì)話的一個(gè)重要標(biāo)識(shí),許多與會(huì)話相關(guān)的運(yùn)行機(jī)制都是基于這個(gè) sessionID 的,因此,無論是哪臺(tái)服務(wù)器為客戶端分配的 sessionID,都務(wù)必保證全局唯一。
Watcher
Watcher(事件監(jiān)聽器),是Zookeeper中的一個(gè)很重要的特性。Zookeeper允許用戶在指定節(jié)點(diǎn)上注冊(cè)一些Watcher,并且在一些特定事件觸發(fā)的時(shí)候,ZooKeeper服務(wù)端會(huì)將事件通知到感興趣的客戶端上去,該機(jī)制是Zookeeper實(shí)現(xiàn)分布式協(xié)調(diào)服務(wù)的重要特性。
ACL
Zookeeper采用ACL(AccessControlLists)策略來進(jìn)行權(quán)限控制,類似于 UNIX 文件系統(tǒng)的權(quán)限控制。Zookeeper 定義了如下5種權(quán)限

四、zookeeper 的數(shù)據(jù)結(jié)構(gòu)
ZooKeeper 允許分布式進(jìn)程通過共享的層次結(jié)構(gòu)命名空間進(jìn)行相互協(xié)調(diào),這與標(biāo)準(zhǔn)文件系統(tǒng)類似。 名稱空間由 ZooKeeper 中的數(shù)據(jù)寄存器組成 - 稱為znode,這些類似于文件和目錄。 與為存儲(chǔ)設(shè)計(jì)的典型文件系統(tǒng)不同,ZooKeeper數(shù)據(jù)保存在內(nèi)存中,這意味著ZooKeeper可以實(shí)現(xiàn)高吞吐量和低延遲。

1、PERSISTENT--持久化目錄節(jié)點(diǎn) 客戶端與zookeeper斷開連接后,該節(jié)點(diǎn)依舊存在
2、PERSISTENT_SEQUENTIAL-持久化順序編號(hào)目錄節(jié)點(diǎn) 客戶端與zookeeper斷開連接后,該節(jié)點(diǎn)依舊存在,只是Zookeeper給該節(jié)點(diǎn)名稱進(jìn)行順序編號(hào)
3、EPHEMERAL-臨時(shí)目錄節(jié)點(diǎn) 客戶端與zookeeper斷開連接后,該節(jié)點(diǎn)被刪除
4、EPHEMERAL_SEQUENTIAL-臨時(shí)順序編號(hào)目錄節(jié)點(diǎn) 客戶端與zookeeper斷開連接后,該節(jié)點(diǎn)被刪除,只是Zookeeper給該節(jié)點(diǎn)名稱進(jìn)行順序編號(hào)
五、zookeeper的作用
1、命名服務(wù)
在zookeeper的文件系統(tǒng)里創(chuàng)建一個(gè)目錄,即有唯一的path,在我們使用tborg無法確定上游程序的部署機(jī)器時(shí)即可與下游程序約定好path,通過path即能互相探索發(fā)現(xiàn)。
2、配置管理
程序總是需要配置的,如果程序分散部署在多臺(tái)機(jī)器上,要逐個(gè)改變配置就變得困難。好吧,現(xiàn)在把這些配置全部放到zookeeper上去,保存在 Zookeeper 的某個(gè)目錄節(jié)點(diǎn)中,然后所有相關(guān)應(yīng)用程序?qū)@個(gè)目錄節(jié)點(diǎn)進(jìn)行監(jiān)聽,一旦配置信息發(fā)生變化,每個(gè)應(yīng)用程序就會(huì)收到 Zookeeper 的通知,然后從 Zookeeper 獲取新的配置信息應(yīng)用到系統(tǒng)中就好。
3、集群管理
所謂集群管理無在乎兩點(diǎn):是否有機(jī)器退出和加入、選舉master。
第一點(diǎn),所有機(jī)器約定在父目錄GroupMembers下創(chuàng)建臨時(shí)目錄節(jié)點(diǎn),然后監(jiān)聽父目錄節(jié)點(diǎn)的子節(jié)點(diǎn)變化消息。一旦有機(jī)器掛掉,該機(jī)器與 zookeeper的連接斷開,其所創(chuàng)建的臨時(shí)目錄節(jié)點(diǎn)被刪除,所有其他機(jī)器都收到通知:某個(gè)兄弟目錄被刪除,于是,所有人都知道他掉線了。新機(jī)器加入 也是類似,所有機(jī)器收到通知:新兄弟目錄加入。
對(duì)于第二點(diǎn),我們稍微改變一下,所有機(jī)器創(chuàng)建臨時(shí)順序編號(hào)目錄節(jié)點(diǎn),每次選取編號(hào)最小的機(jī)器作為master就好。
4、分布式鎖
有了zookeeper的一致性文件系統(tǒng),鎖的問題變得容易。鎖服務(wù)可以分為兩類,一個(gè)是保持獨(dú)占,另一個(gè)是控制時(shí)序。
對(duì)于第一類,我們將zookeeper上的一個(gè)znode看作是一把鎖,通過createznode的方式來實(shí)現(xiàn)。所有客戶端都去創(chuàng)建 /distribute_lock 節(jié)點(diǎn),最終成功創(chuàng)建的那個(gè)客戶端也即擁有了這把鎖。用完刪除掉自己創(chuàng)建的distribute_lock 節(jié)點(diǎn)就釋放出鎖。
對(duì)于第二類, /distribute_lock 已經(jīng)預(yù)先存在,所有客戶端在它下面創(chuàng)建臨時(shí)順序編號(hào)目錄節(jié)點(diǎn),和選master一樣,編號(hào)最小的獲得鎖,用完刪除,依次方便。