ZooKeeper是一個分布式的,開放源碼的分布式應(yīng)用程序協(xié)調(diào)服務(wù),是Google的Chubby一個開源的實現(xiàn),它是集群的管理者,監(jiān)視著集群中各個節(jié)點的狀態(tài)根據(jù)節(jié)點提交的反饋進(jìn)行下一步合理操作。最終,將簡單易用的接口和性能高效、功能穩(wěn)定的系統(tǒng)提供給用戶。
客戶端的讀請求可以被集群中的任意一臺機器處理,如果讀請求在節(jié)點上注冊了監(jiān)聽器,這個監(jiān)聽器也是由所連接的zookeeper機器來處理。對于寫請求,這些請求會同時發(fā)給其他zookeeper機器并且達(dá)成一致后,請求才會返回成功。因此,隨著zookeeper的集群機器增多,讀請求的吞吐會提高但是寫請求的吞吐會下降。
有序性是zookeeper中非常重要的一個特性,所有的更新都是全局有序的,每個更新都有一個唯一的時間戳,這個時間戳稱為zxid(Zookeeper Transaction Id)。而讀請求只會相對于更新有序,也就是讀請求的返回結(jié)果中會帶有這個zookeeper最新的zxid。
2.ZooKeeper提供了什么?
1、文件系統(tǒng)
2、通知機制
3.Zookeeper文件系統(tǒng)
Zookeeper提供一個多層級的節(jié)點命名空間(節(jié)點稱為znode)。與文件系統(tǒng)不同的是,這些節(jié)點都可以設(shè)置關(guān)聯(lián)的數(shù)據(jù),而文件系統(tǒng)中只有文件節(jié)點可以存放數(shù)據(jù)而目錄節(jié)點不行。Zookeeper為了保證高吞吐和低延遲,在內(nèi)存中維護(hù)了這個樹狀的目錄結(jié)構(gòu),這種特性使得Zookeeper不能用于存放大量的數(shù)據(jù),每個節(jié)點的存放數(shù)據(jù)上限為1M。
4.四種類型的znode
1、PERSISTENT-持久化目錄節(jié)點
客戶端與zookeeper斷開連接后,該節(jié)點依舊存在
2、PERSISTENT_SEQUENTIAL-持久化順序編號目錄節(jié)點
客戶端與zookeeper斷開連接后,該節(jié)點依舊存在,只是Zookeeper給該節(jié)點名稱進(jìn)行順序編號
3、EPHEMERAL-臨時目錄節(jié)點
客戶端與zookeeper斷開連接后,該節(jié)點被刪除
4、EPHEMERAL_SEQUENTIAL-臨時順序編號目錄節(jié)點
客戶端與zookeeper斷開連接后,該節(jié)點被刪除,只是Zookeeper給該節(jié)點名稱進(jìn)行順序編號

5.Zookeeper通知機制
client端會對某個znode建立一個watcher事件,當(dāng)該znode發(fā)生變化時,這些client會收到zk的通知,然后client可以根據(jù)znode變化來做出業(yè)務(wù)上的改變等。
6.Zookeeper做了什么?
1、命名服務(wù)
2、配置管理
3、集群管理
4、分布式鎖
5、隊列管理
7.zk的命名服務(wù)(文件系統(tǒng))
命名服務(wù)是指通過指定的名字來獲取資源或者服務(wù)的地址,利用zk創(chuàng)建一個全局的路徑,即是唯一的路徑,這個路徑就可以作為一個名字,指向集群中的集群,提供的服務(wù)的地址,或者一個遠(yuǎn)程的對象等等。
8.zk的配置管理(文件系統(tǒng)、通知機制)
程序分布式的部署在不同的機器上,將程序的配置信息放在zk的znode下,當(dāng)有配置發(fā)生改變時,也就是znode發(fā)生變化時,可以通過改變zk中某個目錄節(jié)點的內(nèi)容,利用watcher通知給各個客戶端,從而更改配置。
9.Zookeeper集群管理(文件系統(tǒng)、通知機制)
所謂集群管理無在乎兩點:是否有機器退出和加入、選舉master。
對于第一點,所有機器約定在父目錄下創(chuàng)建臨時目錄節(jié)點,然后監(jiān)聽父目錄節(jié)點的子節(jié)點變化消息。一旦有機器掛掉,該機器與 zookeeper的連接斷開,其所創(chuàng)建的臨時目錄節(jié)點被刪除,所有其他機器都收到通知:某個兄弟目錄被刪除,于是,所有人都知道:它上船了。
新機器加入也是類似,所有機器收到通知:新兄弟目錄加入,highcount又有了,對于第二點,我們稍微改變一下,所有機器創(chuàng)建臨時順序編號目錄節(jié)點,每次選取編號最小的機器作為master就好。
10.Zookeeper分布式鎖(文件系統(tǒng)、通知機制)
有了zookeeper的一致性文件系統(tǒng),鎖的問題變得容易。鎖服務(wù)可以分為兩類,一個是保持獨占,另一個是控制時序。
對于第一類,我們將zookeeper上的一個znode看作是一把鎖,通過createznode的方式來實現(xiàn)。所有客戶端都去創(chuàng)建 /distribute_lock 節(jié)點,最終成功創(chuàng)建的那個客戶端也即擁有了這把鎖。用完刪除掉自己創(chuàng)建的distribute_lock 節(jié)點就釋放出鎖。
對于第二類, /distribute_lock 已經(jīng)預(yù)先存在,所有客戶端在它下面創(chuàng)建臨時順序編號目錄節(jié)點,和選master一樣,編號最小的獲得鎖,用完刪除,依次方便。
11.獲取分布式鎖的流程

在獲取分布式鎖的時候在locker節(jié)點下創(chuàng)建臨時順序節(jié)點,釋放鎖的時候刪除該臨時節(jié)點。客戶端調(diào)用createNode方法在locker下創(chuàng)建臨時順序節(jié)點,
然后調(diào)用getChildren(“locker”)來獲取locker下面的所有子節(jié)點,注意此時不用設(shè)置任何Watcher。客戶端獲取到所有的子節(jié)點path之后,如果發(fā)現(xiàn)自己創(chuàng)建的節(jié)點在所有創(chuàng)建的子節(jié)點序號最小,那么就認(rèn)為該客戶端獲取到了鎖。
如果發(fā)現(xiàn)自己創(chuàng)建的節(jié)點并非locker所有子節(jié)點中最小的,說明自己還沒有獲取到鎖,此時客戶端需要找到比自己小的那個節(jié)點,然后對其調(diào)用exist()方法,同時對其注冊事件監(jiān)聽器。之后,讓這個被關(guān)注的節(jié)點刪除,則客戶端的Watcher會收到相應(yīng)通知,此時再次判斷自己創(chuàng)建的節(jié)點是否是locker子節(jié)點中序號最小的,如果是則獲取到了鎖,如果不是則重復(fù)以上步驟繼續(xù)獲取到比自己小的一個節(jié)點并注冊監(jiān)聽。當(dāng)前這個過程中還需要許多的邏輯判斷。

代碼的實現(xiàn)主要是基于互斥鎖,獲取分布式鎖的重點邏輯在于BaseDistributedLock,實現(xiàn)了基于Zookeeper實現(xiàn)分布式鎖的細(xì)節(jié)。
12.Zookeeper隊列管理(文件系統(tǒng)、通知機制)
兩種類型的隊列:
- 同步隊列,當(dāng)一個隊列的成員都聚齊時,這個隊列才可用,否則一直等待所有成員到達(dá)。
- 隊列按照 FIFO 方式進(jìn)行入隊和出隊操作。
第一類,在約定目錄下創(chuàng)建臨時目錄節(jié)點,監(jiān)聽節(jié)點數(shù)目是否是我們要求的數(shù)目。
第二類,和分布式鎖服務(wù)中的控制時序場景基本原理一致,入列有編號,出列按編號。在特定的目錄下創(chuàng)建PERSISTENT_SEQUENTIAL節(jié)點,創(chuàng)建成功時Watcher通知等待的隊列,隊列刪除序列號最小的節(jié)點用以消費。此場景下Zookeeper的znode用于消息存儲,znode存儲的數(shù)據(jù)就是消息隊列中的消息內(nèi)容,SEQUENTIAL序列號就是消息的編號,按序取出即可。由于創(chuàng)建的節(jié)點是持久化的,所以不必?fù)?dān)心隊列消息的丟失問題。
13.Zookeeper數(shù)據(jù)復(fù)制
Zookeeper作為一個集群提供一致的數(shù)據(jù)服務(wù),自然,它要在所有機器間做數(shù)據(jù)復(fù)制。數(shù)據(jù)復(fù)制的好處:
- 容錯:一個節(jié)點出錯,不致于讓整個系統(tǒng)停止工作,別的節(jié)點可以接管它的工作;
- 提高系統(tǒng)的擴展能力 :把負(fù)載分布到多個節(jié)點上,或者增加節(jié)點來提高系統(tǒng)的負(fù)載能力;
- 提高性能:讓客戶端本地訪問就近的節(jié)點,提高用戶訪問速度。
從客戶端讀寫訪問的透明度來看,數(shù)據(jù)復(fù)制集群系統(tǒng)分下面兩種:
- 寫主(WriteMaster) :對數(shù)據(jù)的修改提交給指定的節(jié)點。讀無此限制,可以讀取任何一個節(jié)點。這種情況下客戶端需要對讀與寫進(jìn)行區(qū)別,俗稱讀寫分離;
- 寫任意(Write Any):對數(shù)據(jù)的修改可提交給任意的節(jié)點,跟讀一樣。這種情況下,客戶端對集群節(jié)點的角色與變化透明。
對zookeeper來說,它采用的方式是寫任意。通過增加機器,它的讀吞吐能力和響應(yīng)能力擴展性非常好,而寫,隨著機器的增多吞吐能力肯定下降(這也是它建立observer的原因),而響應(yīng)能力則取決于具體實現(xiàn)方式,是延遲復(fù)制保持最終一致性,還是立即復(fù)制快速響應(yīng)。
14.Zookeeper工作原理
Zookeeper 的核心是原子廣播,這個機制保證了各個Server之間的同步。實現(xiàn)這個機制的協(xié)議叫做Zab協(xié)議。Zab協(xié)議有兩種模式,它們分別是恢復(fù)模式(選主)和廣播模式(同步)。當(dāng)服務(wù)啟動或者在領(lǐng)導(dǎo)者崩潰后,Zab就進(jìn)入了恢復(fù)模式,當(dāng)領(lǐng)導(dǎo)者被選舉出來,且大多數(shù)Server完成了和 leader的狀態(tài)同步以后,恢復(fù)模式就結(jié)束了。狀態(tài)同步保證了leader和Server具有相同的系統(tǒng)狀態(tài)。
15.zookeeper是如何保證事務(wù)的順序一致性的?
zookeeper采用了遞增的事務(wù)Id來標(biāo)識,所有的proposal(提議)都在被提出的時候加上了zxid,zxid實際上是一個64位的數(shù)字,高32位是epoch(時期; 紀(jì)元; 世; 新時代)用來標(biāo)識leader是否發(fā)生改變,如果有新的leader產(chǎn)生出來,epoch會自增,低32位用來遞增計數(shù)。當(dāng)新產(chǎn)生proposal的時候,會依據(jù)數(shù)據(jù)庫的兩階段過程,首先會向其他的server發(fā)出事務(wù)執(zhí)行請求,如果超過半數(shù)的機器都能執(zhí)行并且能夠成功,那么就會開始執(zhí)行。
16.Zookeeper 下 Server工作狀態(tài)
每個Server在工作過程中有三種狀態(tài):
- LOOKING:當(dāng)前Server不知道leader是誰,正在搜尋
- LEADING:當(dāng)前Server即為選舉出來的leader
- FOLLOWING:leader已經(jīng)選舉出來,當(dāng)前Server與之同步
17.zookeeper是如何選取主leader的?
當(dāng)leader崩潰或者leader失去大多數(shù)的follower,這時zk進(jìn)入恢復(fù)模式,恢復(fù)模式需要重新選舉出一個新的leader,讓所有的Server都恢復(fù)到一個正確的狀態(tài)。Zk的選舉算法有兩種:一種是基于basic paxos實現(xiàn)的,另外一種是基于fast paxos算法實現(xiàn)的。系統(tǒng)默認(rèn)的選舉算法為fast paxos。
1、Zookeeper選主流程(basic paxos)
1.選舉線程由當(dāng)前Server發(fā)起選舉的線程擔(dān)任,其主要功能是對投票結(jié)果進(jìn)行統(tǒng)計,并選出推薦的Server;
2.選舉線程首先向所有Server發(fā)起一次詢問(包括自己);
3.選舉線程收到回復(fù)后,驗證是否是自己發(fā)起的詢問(驗證zxid是否一致),然后獲取對方的id(myid),并存儲到當(dāng)前詢問對象列表中,最后獲取對方提議的leader相關(guān)信息(id,zxid),并將這些信息存儲到當(dāng)次選舉的投票記錄表中;
4.收到所有Server回復(fù)以后,就計算出zxid最大的那個Server,并將這個Server相關(guān)信息設(shè)置成下一次要投票的Server;
5.線程將當(dāng)前zxid最大的Server設(shè)置為當(dāng)前Server要推薦的Leader,如果此時獲勝的Server獲得n/2 + 1的Server票數(shù),設(shè)置當(dāng)前推薦的leader為獲勝的Server,將根據(jù)獲勝的Server相關(guān)信息設(shè)置自己的狀態(tài),否則,繼續(xù)這個過程,直到leader被選舉出來。通過流程分析我們可以得出:要使Leader獲得多數(shù)Server的支持,則Server總數(shù)必須是奇數(shù)2n+1,且存活的Server的數(shù)目不得少于n+1. 每個Server啟動后都會重復(fù)以上流程。在恢復(fù)模式下,如果是剛從崩潰狀態(tài)恢復(fù)的或者剛啟動的server還會從磁盤快照中恢復(fù)數(shù)據(jù)和會話信息,zk會記錄事務(wù)日志并定期進(jìn)行快照,方便在恢復(fù)時進(jìn)行狀態(tài)恢復(fù)。

2、Zookeeper選主流程(basic paxos)
fast paxos流程是在選舉過程中,某Server首先向所有Server提議自己要成為leader,當(dāng)其它Server收到提議以后,解決epoch和 zxid的沖突,并接受對方的提議,然后向?qū)Ψ桨l(fā)送接受提議完成的消息,重復(fù)這個流程,最后一定能選舉出Leader。

18.Zookeeper同步流程
選完Leader以后,zk就進(jìn)入狀態(tài)同步過程。
- Leader等待server連接;
- Follower連接leader,將最大的zxid發(fā)送給leader;
- Leader根據(jù)follower的zxid確定同步點;
- 完成同步后通知follower 已經(jīng)成為uptodate狀態(tài);
- Follower收到uptodate消息后,又可以重新接受client的請求進(jìn)行服務(wù)了。
19.分布式通知和協(xié)調(diào)
對于系統(tǒng)調(diào)度來說:操作人員發(fā)送通知實際是通過控制臺改變某個節(jié)點的狀態(tài),然后zk將這些變化發(fā)送給注冊了這個節(jié)點的watcher的所有客戶端。
對于執(zhí)行情況匯報:每個工作進(jìn)程都在某個目錄下創(chuàng)建一個臨時節(jié)點。并攜帶工作的進(jìn)度數(shù)據(jù),這樣匯總的進(jìn)程可以監(jiān)控目錄子節(jié)點的變化獲得工作進(jìn)度的實時的全局情況。

20.機器中為什么會有l(wèi)eader?
在分布式環(huán)境中,有些業(yè)務(wù)邏輯只需要集群中的某一臺機器進(jìn)行執(zhí)行,其他的機器可以共享這個結(jié)果,這樣可以大大減少重復(fù)計算,提高性能,于是就需要進(jìn)行l(wèi)eader選舉。
21.zk節(jié)點宕機如何處理?
Zookeeper本身也是集群,推薦配置不少于3個服務(wù)器。Zookeeper自身也要保證當(dāng)一個節(jié)點宕機時,其他節(jié)點會繼續(xù)提供服務(wù)。
如果是一個Follower宕機,還有2臺服務(wù)器提供訪問,因為Zookeeper上的數(shù)據(jù)是有多個副本的,數(shù)據(jù)并不會丟失;
如果是一個Leader宕機,Zookeeper會選舉出新的Leader。
ZK集群的機制是只要超過半數(shù)的節(jié)點正常,集群就能正常提供服務(wù)。只有在ZK節(jié)點掛得太多,只剩一半或不到一半節(jié)點能工作,集群才失效。
所以
- 3個節(jié)點的cluster可以掛掉1個節(jié)點(leader可以得到2票>1.5)
- 2個節(jié)點的cluster就不能掛掉任何1個節(jié)點了(leader可以得到1票<=1)
22.zookeeper負(fù)載均衡和nginx負(fù)載均衡區(qū)別
zk的負(fù)載均衡是可以調(diào)控,nginx只是能調(diào)權(quán)重,其他需要可控的都需要自己寫插件;但是nginx的吞吐量比zk大很多,應(yīng)該說按業(yè)務(wù)選擇用哪種方式。
23.zookeeper watch機制
Watch機制官方聲明:一個Watch事件是一個一次性的觸發(fā)器,當(dāng)被設(shè)置了Watch的數(shù)據(jù)發(fā)生了改變的時候,則服務(wù)器將這個改變發(fā)送給設(shè)置了Watch的客戶端,以便通知它們。
Zookeeper機制的特點:
1、一次性觸發(fā)數(shù)據(jù)發(fā)生改變時,一個watcher event會被發(fā)送到client,但是client只會收到一次這樣的信息。
2、watcher event異步發(fā)送watcher的通知事件從server發(fā)送到client是異步的,這就存在一個問題,不同的客戶端和服務(wù)器之間通過socket進(jìn)行通信,由于網(wǎng)絡(luò)延遲或其他因素導(dǎo)致客戶端在不通的時刻監(jiān)聽到事件,由于Zookeeper本身提供了ordering guarantee,即客戶端監(jiān)聽事件后,才會感知它所監(jiān)視znode發(fā)生了變化。所以我們使用Zookeeper不能期望能夠監(jiān)控到節(jié)點每次的變化。Zookeeper只能保證最終的一致性,而無法保證強一致性。
3、數(shù)據(jù)監(jiān)視Zookeeper有數(shù)據(jù)監(jiān)視和子數(shù)據(jù)監(jiān)視getdata() and exists()設(shè)置數(shù)據(jù)監(jiān)視,getchildren()設(shè)置了子節(jié)點監(jiān)視。
4、注冊watcher getData、exists、getChildren
5、觸發(fā)watcher create、delete、setData
6、setData()會觸發(fā)znode上設(shè)置的data watch(如果set成功的話)。一個成功的create() 操作會觸發(fā)被創(chuàng)建的znode上的數(shù)據(jù)watch,以及其父節(jié)點上的child watch。而一個成功的delete()操作將會同時觸發(fā)一個znode的data watch和child watch(因為這樣就沒有子節(jié)點了),同時也會觸發(fā)其父節(jié)點的child watch。
7、當(dāng)一個客戶端連接到一個新的服務(wù)器上時,watch將會被以任意會話事件觸發(fā)。當(dāng)與一個服務(wù)器失去連接的時候,是無法接收到watch的。而當(dāng)client重新連接時,如果需要的話,所有先前注冊過的watch,都會被重新注冊。通常這是完全透明的。只有在一個特殊情況下,watch可能會丟失:對于一個未創(chuàng)建的znode的exist watch,如果在客戶端斷開連接期間被創(chuàng)建了,并且隨后在客戶端連接上之前又刪除了,這種情況下,這個watch事件可能會被丟失。
8、Watch是輕量級的,其實就是本地JVM的Callback,服務(wù)器端只是存了是否有設(shè)置了Watcher的布爾類型