天天看點

分布式系統的一緻性協定之 Zookeeper一緻性協定ZAB簡述

zookeeper是使用ZAB協定作為資料一緻性的算法,ZAB全稱:原子消息廣播協定。

ZAB可以說是在Paxos算法基礎上進行了擴充改造:

1、ZAB協定設計了支援崩潰恢複,可以保證在Leader程序崩潰的時候重新選舉出Leader并且保證資料的完整性

2、zookeeper使用單一主程序Leader用于處理用戶端所有事物請求,寫隻能由Leader處理,寫過程會阻塞讀程序

3、采用ZAB協定将伺服器狀态以事務形式廣播到所有follower上

4、由于事物間可能存在着依賴關系,ZAB協定保證Leader廣播的變更序列被順序的處理

ZAB分為恢複階段和廣播階段,用于處理不同狀态。

廣播階段

zookeeper在處理事務(寫)時,采用優化的2PC協定(多數派):

在ZooKeeper中所有的事務請求都由一個主伺服器也就是Leader來處理,其他伺服器為Follower,Leader将用戶端的事務請求轉換為事務Proposal,并且将Proposal分發給叢集中其他所有的Follower,然後Leader等待Follwer回報,當有過半數(>=N/2+1) 的Follower回報資訊後,Leader将再次向叢集内Follower廣播Commit資訊,Commit為将之前的Proposal送出。

崩潰恢複階段

當Leader當機時,先進入選舉階段,再進入恢複階段:

選舉是通過follower的ZXID選出的,最大的ZXID會認為是最新的,擁有最大ZXID的Follower會被選舉為新Leader.

恢複包括discovery和sync兩個場景,discovery是follower節點向準Leader發送上一周期(epoch)資訊,與Leader互相校驗epoch進度,确定後通過sync場景進行資料同步,由Leader發起同步指令,最終保持叢集資料的一緻性。

ZAB協定中存在着三種狀态,每個節點都屬于以下三種中的一種:

1、Looking:系統剛啟動或Leader崩潰後正處于選舉狀态

2、Following:Follower節點所處的狀态,Follower與Leader處于資料同步階段時的狀态

3、Leading:Leader節點所處的狀态:目前叢集中有一個Leader為主程序。

ZooKeeper啟動時所有節點初始狀态為Looking,這時叢集會嘗試選舉出一個Leader節點,選舉出的Leader節點切換為Leading狀态;當節點發現叢集中已經選舉出Leader則該節點會切換到Following狀态,然後和Leader節點保持同步;當Follower節點與Leader失去聯系時Follower節點則會切換到Looking狀态,開始新一輪選舉;在ZooKeeper的整個生命周期中每個節點都會在Looking、Following、Leading狀态間不斷轉換;

epoch是什麼?

ZAB協定中使用ZXID作為事務編号,ZXID為64位數字,低32位為一個遞增的計數器,每一個用戶端的一個事務請求時Leader産生新的事務後該計數器都會加1,高32位為Leader周期epoch編号,當新選舉出一個Leader節點時Leader會取出本地日志中最大事務Proposal的ZXID解析出對應的epoch把該值加1作為新的epoch,将低32位從0開始生成新的ZXID;ZAB使用epoch來區分不同的Leader周期;(zookeeper是以此來保證事務的順序一緻性)。

廣播(Broadcast)流程

用戶端送出事務請求時Leader節點為每一個請求生成一個事務Proposal,将其發送給叢集中所有的Follower節點,收到過半Follower的回報後開始對事務進行送出,ZAB協定使用了原子廣播協定;在ZAB協定中隻需要得到過半的Follower節點回報Ack就可以對事務進行送出,這也導緻了Leader幾點崩潰後可能會出現資料不一緻的情況,ZAB使用了崩潰恢複來處理數字不一緻問題;消息廣播使用了TCP協定進行通訊所有保證了接受和發送事務的順序性。廣播消息時Leader節點為每個事務Proposal配置設定一個全局遞增的ZXID(事務ID),每個事務Proposal都按照ZXID順序來處理;

zookeeper是如何選取主leader的?

當leader崩潰或者leader失去大多數的follower,這時zk進入恢複模式,恢複模式需要重新選舉出一個新的leader,讓所有的Server都恢複到一個正确的狀态。Zk的選舉算法有兩種:一種是基于basic paxos實作的,另外一種是基于fast paxos算法實作的。系統預設的選舉算法為fast paxos。

1、Zookeeper選主流程(basic paxos)

(1)選舉線程由目前Server發起選舉的線程擔任,其主要功能是對投票結果進行統計,并選出推薦的Server; 

(2)選舉線程首先向所有Server發起一次詢問(包括自己); 

(3)選舉線程收到回複後,驗證是否是自己發起的詢問(驗證zxid是否一緻),然後擷取對方的id(myid),并存儲到目前詢問對象清單中,最後擷取對方提議的leader相關資訊(id,zxid),并将這些資訊存儲到當次選舉的投票記錄表中; 

(4)收到所有Server回複以後,就計算出zxid最大的那個Server,并将這個Server相關資訊設定成下一次要投票的Server; 

(5)線程将目前zxid最大的Server設定為目前Server要推薦的Leader,如果此時獲勝的Server獲得n/2 + 1的Server票數,設定目前推薦的leader為獲勝的Server,将根據獲勝的Server相關資訊設定自己的狀态,否則,繼續這個過程,直到leader被選舉出來。 通過流程分析我們可以得出:要使Leader獲得多數Server的支援,則Server總數必須是奇數2n+1,且存活的Server的數目不得少于n+1. 每個Server啟動後都會重複以上流程。在恢複模式下,如果是剛從崩潰狀态恢複的或者剛啟動的server還會從磁盤快照中恢複資料和會話資訊,zk會記錄事務日志并定期進行快照,友善在恢複時進行狀态恢複。

分布式系統的一緻性協定之 Zookeeper一緻性協定ZAB簡述

2、Zookeeper選主流程(fast paxos)

fast paxos流程是在選舉過程中,某Server首先向所有Server提議自己要成為leader,當其它Server收到提議以後,解決epoch和 zxid的沖突,并接受對方的提議,然後向對方發送接受提議完成的消息,重複這個流程,最後一定能選舉出Leader。

分布式系統的一緻性協定之 Zookeeper一緻性協定ZAB簡述

Zookeeper同步流程

選完Leader以後,zk就進入狀态同步過程。 

1、Leader等待server連接配接; 

2、Follower連接配接leader,将最大的zxid發送給leader; 

3、Leader根據follower的zxid确定同步點; 

4、完成同步後通知follower 已經成為uptodate狀态; 

5、Follower收到uptodate消息後,又可以重新接受client的請求進行服務了。

 zk節點當機如何處理?

Zookeeper本身也是叢集,推薦配置不少于3個伺服器。Zookeeper自身也要保證當一個節點當機時,其他節點會繼續提供服務。

如果是一個Follower當機,還有2台伺服器提供通路,因為Zookeeper上的資料是有多個副本的,資料并不會丢失;

如果是一個Leader當機,Zookeeper會選舉出新的Leader。

ZK叢集的機制是隻要超過半數的節點正常,叢集就能正常提供服務。隻有在ZK節點挂得太多,隻剩一半或不到一半節點能工作,叢集才失效。

是以:

3個節點的cluster可以挂掉1個節點(leader可以得到2票>1.5)

2個節點的cluster就不能挂掉任何1個節點了(leader可以得到1票<=1)

zookeeper負載均衡和nginx負載均衡差別

zk的負載均衡是可以調控,nginx隻是能調權重,其他需要可控的都需要自己寫插件;但是nginx的吞吐量比zk大很多,應該說按業務選擇用哪種方式。

zookeeper watch機制

Watch機制官方聲明:一個Watch事件是一個一次性的觸發器,當被設定了Watch的資料發生了改變的時候,則伺服器将這個改變發送給設定了Watch的用戶端,以便通知它們。

Zookeeper機制的特點:

1、一次性觸發資料發生改變時,一個watcher event會被發送到client,但是client隻會收到一次這樣的資訊。

2、watcher event異步發送watcher的通知事件從server發送到client是異步的,這就存在一個問題,不同的用戶端和伺服器之間通過socket進行通信,由于網絡延遲或其他因素導緻用戶端在不通的時刻監聽到事件,由于Zookeeper本身提供了ordering guarantee,即用戶端監聽事件後,才會感覺它所監視znode發生了變化。是以我們使用Zookeeper不能期望能夠監控到節點每次的變化。Zookeeper隻能保證最終的一緻性,而無法保證強一緻性。

3、資料監視Zookeeper有資料監視和子資料監視getdata() and exists()設定資料監視,getchildren()設定了子節點監視。

4、注冊watcher getData、exists、getChildren

5、觸發watcher create、delete、setData

6、setData()會觸發znode上設定的data watch(如果set成功的話)。一個成功的create() 操作會觸發被建立的znode上的資料watch,以及其父節點上的child watch。而一個成功的delete()操作将會同時觸發一個znode的data watch和child watch(因為這樣就沒有子節點了),同時也會觸發其父節點的child watch。

7、當一個用戶端連接配接到一個新的伺服器上時,watch将會被以任意會話事件觸發。當與一個伺服器失去連接配接的時候,是無法接收到watch的。而當client重新連接配接時,如果需要的話,所有先前注冊過的watch,都會被重新注冊。通常這是完全透明的。隻有在一個特殊情況下,watch可能會丢失:對于一個未建立的znode的exist watch,如果在用戶端斷開連接配接期間被建立了,并且随後在用戶端連接配接上之前又删除了,這種情況下,這個watch事件可能會被丢失。

8、Watch是輕量級的,其實就是本地JVM的Callback,伺服器端隻是存了是否有設定了Watcher的布爾類型。

Zookeeper資料複制

Zookeeper作為一個叢集提供一緻的資料服務,自然,它要在所有機器間做資料複制。資料複制的好處: 

1、容錯:一個節點出錯,不緻于讓整個系統停止工作,别的節點可以接管它的工作; 

2、提高系統的擴充能力 :把負載分布到多個節點上,或者增加節點來提高系統的負載能力; 

3、提高性能:讓用戶端本地通路就近的節點,提高使用者通路速度。

從用戶端讀寫通路的透明度來看,資料複制叢集系統分下面兩種: 

1、寫主(WriteMaster) :對資料的修改送出給指定的節點。讀無此限制,可以讀取任何一個節點。這種情況下用戶端需要對讀與寫進行差別,俗稱讀寫分離; 

2、寫任意(Write Any):對資料的修改可送出給任意的節點,跟讀一樣。這種情況下,用戶端對叢集節點的角色與變化透明。

對zookeeper來說,它采用的方式是寫任意。通過增加機器,它的讀吞吐能力和響應能力擴充性非常好,而寫,随着機器的增多吞吐能力肯定下降(這也是它建立observer的原因),而響應能力則取決于具體實作方式,是延遲複制保持最終一緻性,還是立即複制快速響應。

參考:

ZooKeeper之ZAB協定 - 推酷

https://www.tuicool.com/articles/IfQR3u3

Paxos-->Fast Paxos-->Zookeeper分析 - Kuzury - CSDN部落格

https://blog.csdn.net/u010039929/article/details/70171672

zookeeper面試題 - 個人文章 - SegmentFault 思否

https://segmentfault.com/a/1190000014479433