ZooKeeper集羣與Leader選舉

 ZooKeeper是一個開源分佈式協調服務、分佈式數據一致性解決方案。可基於ZooKeeper實現命名服務、集羣管理、Master選舉、分佈式鎖等功能。java

  高可用  算法

爲了保證ZooKeeper的可用性,在生產環境中咱們使用ZooKeeper集羣模式對外提供服務,而且集羣規模至少由3個ZooKeeper節點組成。安全



  集羣至少由3個節點組成  服務器

ZooKeeper其實2個節點也能夠組成集羣並對外提供服務,但咱們使用集羣主要目的是爲了高可用。若是2個節點組成集羣,其中1個節點掛了,另外ZooKeeper節點不能正常對外提供服務。所以也失去了集羣的意義。網絡

若是3個節點組成集羣,其中1個節點掛掉後,根據ZooKeeper的Leader選舉機制是能夠從另外2個節點選出一個做爲Leader的,集羣能夠繼續對外提供服務。架構



  並不是節點越多越好  併發

  • 節點越多,使用的資源越多分佈式

  • 節點越多,ZooKeeper節點間花費的通信成本越高,節點間互連的Socket也越多。影響ZooKeeper集羣事務處理ui

  • 節點越多,形成腦裂的可能性越大spa



  集羣規模爲奇數  

集羣規模除了考慮自身成本和資源外還要結合ZooKeeper特性考慮:

  • 節省資源

    3節點集羣和4節點集羣,咱們選擇使用3節點集羣;5節點集羣和6節點集羣,咱們選擇使用5節點集羣。以此類推。由於生產環境爲了保證高可用,3節點集羣最多隻容許掛1臺,4節點集羣最多也只容許掛1臺(過半原則中解釋了緣由)。同理5節點集羣最多容許掛2臺,6節點集羣最多也只容許掛2臺。

    出於對資源節省的考慮,咱們應該使用奇數節點來知足相同的高可用性。

  • 集羣可用性

    當集羣中節點間網絡通信出現問題時奇數和偶數對集羣的影響







  集羣配置  

ZooKeeper集羣配置至少須要2處變動:

一、增長集羣配置

在{ZK_HOME}/conf/zoo.cfg中增長集羣的配置,結構以server.id=ip:port1:port2爲標準。

好比下面配置文件中表示由3個ZooKeeper組成的集羣:

server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883

 

 

二、配置節點id

zoo.cfg中配置集羣時須要指定server.id,這個id須要在dataDir(zoo.cfg中配置)指定的目錄中建立myid文件,文件內容就是當前ZooKeeper節點的id。

  集羣角色  

ZooKeeper沒有使用Master/Slave的概念,而是將集羣中的節點分爲了3類角色:

  • Leader

    在一個ZooKeeper集羣中,只能存在一個Leader,這個Leader是集羣中事務請求惟一的調度者和處理者,所謂事務請求是指會改變集羣狀態的請求;Leader根據事務ID能夠保證事務處理的順序性。

    若是一個集羣中存在多個Leader,這種現象稱爲「腦裂」。試想一下,一個集羣中存在多個Leader會產生什麼影響?

    至關於本來一個大集羣,裂出多個小集羣,他們之間的數據是不會相互同步的。「腦裂」後集羣中的數據會變得很是混亂。

  • Follower

    Follower角色的ZooKeeper服務只能處理非事務請求;若是接收到客戶端事務請求會將請求轉發給Leader服務器;參與Leader選舉;參與Leader事務處理投票處理。

    Follower發現集羣中Leader不可用時會變動自身狀態,併發起Leader選舉投票,最終集羣中的某個Follower會被選爲Leader。

  • Observer

    Observer與Follower很像,能夠處理非事務請求;將事務請求轉發給Leader服務器。

    與Follower不一樣的是,Observer不會參與Leader選舉;不會參與Leader事務處理投票。

    Observer用於不影響集羣事務處理能力的前提下提高集羣的非事務處理能力。

  Leader選舉  

Leader在集羣中是很是重要的一個角色,負責了整個事務的處理和調度,保證分佈式數據一致性的關鍵所在。既然Leader在ZooKeeper集羣中這麼重要因此必定要保證集羣在任什麼時候候都有且僅有一個Leader存在。

若是集羣中Leader不可用了,須要有一個機制來保證能從集羣中找出一個最優的服務晉升爲Leader繼續處理事務和調度等一系列職責。這個過程稱爲Leader選舉。



  選舉機制  

ZooKeeper選舉Leader依賴下列原則並遵循優先順序:

一、選舉投票必須在同一輪次中進行

若是Follower服務選舉輪次不一樣,不會採納投票。

二、數據最新的節點優先成爲Leader

數據的新舊使用事務ID斷定,事務ID越大認爲節點數據約接近Leader的數據,天然應該成爲Leader。

三、比較server.id,id值大的優先成爲Leader

若是每一個參與競選節點事務ID同樣,再使用server.id作比較。server.id是節點在集羣中惟一的id,myid文件中配置。

不論是在集羣啓動時選舉Leader仍是集羣運行中從新選舉Leader。集羣中每一個Follower角色服務都是以上面的條件做爲基礎推選出合適的Leader,一旦出現某個節點被過半推選,那麼該節點晉升爲Leader。



  過半原則  

ZooKeeper集羣會有不少類型投票。Leader選舉投票;事務提議投票;這些投票依賴過半原則。就是說ZooKeeper認爲投票結果超過了集羣總數的一半,即可以安全的處理後續事務。

  • 事務提議投票

    假設有3個節點組成ZooKeeper集羣,客戶端請求添加一個節點。Leader接到該事務請求後給全部Follower發起「建立節點」的提議投票。若是Leader收到了超過集羣一半數量的反饋,繼續給全部Follower發起commit。此時Leader認爲集羣過半了,就算本身掛了集羣也是安全可靠的。

  • Leader選舉投票

    假設有3個節點組成ZooKeeper集羣,這時Leader掛了,須要投票選舉Leader。當相同投票結果過半後Leader選出。

  • 集羣可用節點

    ZooKeeper集羣中每一個節點有本身的角色,對於集羣可用性來講必須知足過半原則。這個過半是指Leader角色 + Follower角色可用數大於集羣中Leader角色 + Follower角色總數。
    假設有5個節點組成ZooKeeper集羣,一個Leader、兩個Follower、兩個Observer。當掛掉兩個Follower或掛掉一個Leader和一個Follower時集羣將不可用。由於Observer角色不參與任何形式的投票。

所謂過半原則算法是說票數 > 集羣總節點數/2。其中集羣總節點數/2的計算結果會向下取整。

在ZooKeeper源代碼QuorumMaj.java中實現了這個算法。下面代碼片斷有所縮減。

public boolean containsQuorum(HashSet<Long> set) {
  /** n是指集羣總數 */
  int half = n / 2;
  return (set.size() > half);
}

回過頭咱們看一下奇數和偶數集羣在Leader選舉的結果

因此3節點和4節點組成的集羣在ZooKeeper過半原則下都最多隻能掛1節點,可是4比3要多浪費一個節點資源。

  場景實戰  

咱們以兩個場景來了解集羣不可用時Leader從新選舉的過程。



  3節點集羣重選Leader  

假設有3節點組成的集羣,分別是server.1(Follower)、server.2(Leader)、server.3(Follower)。此時server.2不可用了。集羣會產生如下變化:

一、集羣不可用

由於Leader掛了,集羣不可用於事務請求了。

二、狀態變動

全部Follower節點變動自身狀態爲LOOKING,而且變動自身投票。投票內容就是本身節點的事務ID和server.id。咱們以(事務ID, server.id)表示。

假設server.1的事務id是10,變動的自身投票就是(10, 1);server.3的事務id是8,變動的自身投票就是(8, 3)。

三、首輪投票

將變動的投票發給集羣中全部的Follower節點。server.1將(10, 1)發給集羣中全部Follower,包括它本身。server.3也同樣,將(8, 3)發給全部Follower。

因此server.1將收到(10, 1)和(8, 3)兩個投票,server.3將收到(8, 3)和(10, 1)兩個投票。

四、投票PK

每一個Follower節點除了發起投票外,還接其餘Follower發來的投票,並與本身的投票PK(比較兩個提議的事務ID以及server.id),PK結果決定是否要變動自身狀態並再次投票。

對於server.1來講收到(10, 1)和(8, 3)兩個投票,與本身變動的投票比較後沒有一個比自身投票(10, 1)要大的,因此server.1維持自身投票不變。

對於server.3來講收到(10, 1)和(8, 3)兩個投票,與自身變動的投票比較後認爲server.1發來的投票要比自身的投票大,因此server.3會變動自身投票並將變動後的投票發給集羣中全部Follower。

五、第二輪投票

server.3將自身投票變動爲(10, 1)後再次將投票發給集羣中全部Follower。

對於server.1來講在第二輪收到了(10, 1)投票,server.1通過PK後繼續維持不變。

對於server.3來講在第二輪收到了(10, 1)投票,由於server.3自身已變動爲(10, 3)投票,因此本次也維持不變。

此時server.1和server.3在投票上達成一致。

六、投票接收桶

節點接收的投票存儲在一個接收桶裏,每一個Follower的投票結果在桶內只記錄一次。ZooKeeper源碼中接收桶用Map實現。

下面代碼片斷是ZooKeeper定義的接收桶,以及向桶內寫入數據。Map.Key是Long類型,用來存儲投票來源節點的server.id,Vote則是對應節點的投票信息。節點收到投票後會更新這個接收桶,也就是說桶裏存儲了全部Follower節點的投票而且僅存最後一次的投票結果。

HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));

七、統計投票

接收到投票後每次都會嘗試統計投票,投票統計過半後選舉成功。

投票統計的數據來源於投票接收桶裏的投票數據,咱們從頭描述這個場景,來看一下接收桶裏的數據變化狀況。

server.2掛了後,server.1和server.3發起第一輪投票。

server.1接收到來自server.1的(10, 1)投票和來自server.3的(8, 3)投票。

server.3一樣接收到來自server.1的(10, 1)投票和來自server.3的(8, 3)投票。此時server.1和server.3接收桶裏的數據是這樣的:

server.3通過PK後認爲server.1的選票比本身要大,因此變動了本身的投票並從新發起投票。

server.1收到了來自server.3的(10, 1)投票;server.3收到了來自sever.3的(10, 1)投票。此時server.1和server.3接收桶裏的數據變成了這樣:

基於ZooKeeper過半原則:桶內投票選舉server.1做爲Leader出現2次,知足了過半 2 > 3/2 即 2>1。

最後sever.1節點晉升爲Leader,server.3變動爲Follower。



 集羣擴容Leader啓動時機 

ZooKeeper集羣擴容須要在zoo.cfg配置文件中加入新節點。擴容流程在ZooKeeper擴容中介紹。這裏咱們以3節點擴容到5節點時,Leader啓動時機作一個討論。

假設目前有3個節點組成集羣,分別是server.1(Follower)、server.2(Leader)、server.3(Follower),假設集羣中節點事務ID相同。配置文件以下。

server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883

一、新節點加入集羣

集羣中新增server.4和server.5兩個節點,首先修改server.4和server.5的zoo.cfg配置並啓動。節點4和5在啓動後會變動自身投票狀態,發起一輪Leader選舉投票。server.一、server.二、server.3收到投票後因爲集羣中已有選定Leader,因此會直接反饋server.4和server.5投票結果:server.2是Leader。server.4和server.5收到投票後基於過半原則認定server.2是Leader,自身便切換爲Follower。

#節點server.一、server.二、server.3配置
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883

#節點server.四、server.5配置
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883
server.4=localhost:2884:3884
server.5=localhost:2885:3885

二、中止Leader

server.4和server.5的加入須要修改集羣server.一、server.二、server.3的zoo.cfg配置並重啓。可是Leader節點什麼時候重啓是有講究的,由於Leader重啓會致使集羣中Follower發起Leader從新選舉。在server.4和server.5兩個新節點正常加入後,集羣不會由於新節點加入變動Leader,因此目前server.2依然是Leader。

咱們以一個錯誤的順序啓動,看一下集羣會發生什麼樣的變化。修改server.2zoo.cfg配置文件,增長server.4和server.5的配置並中止server.2服務。中止server.2後,Leader不存在了,集羣中全部Follower會發起投票。當server.1和server.3發起投票時並不會將投票發給server.4和server.5,由於在server.1和server.3的集羣配置中不包含server.4和server.5節點。相反,server.4和server.5會把選票發給集羣中全部節點。也就是說對於server.1和server.3他們認爲集羣中只有3個節點。對於server.4和server.5他們認爲集羣中有5個節點。

根據過半原則,server.1和server.3很快會選出一個新Leader,咱們這裏假設server.3晉級成爲了新Leader。可是咱們沒有啓動server.2的狀況下,由於投票不知足過半原則,server.4和server.5會一直作投票選舉Leader的動做。截止到如今集羣中節點狀態是這樣的:

三、啓動Leader

如今,咱們啓動server.2。由於server.2zoo.cfg已是server.1到serverv.5的全量配置,在server.2啓動後會發起選舉投票,同時serverv.4和serverv.5也在不斷的發起選舉投票。當server.2的選舉輪次和serverv.4與serverv.5選舉輪次對齊後,最終server.2會變動本身的狀態,認定server.5是Leaader。

意想不到的事情發生了,出現兩個Leader:

ZooKeeper集羣擴容時,若是Leader節點最後啓動就能夠避免這類問題發生,由於在Leader節點重啓前,全部的Follower節點zoo.cfg配置已是相同的,他們基於同一個集羣配置兩兩互聯,作投票選舉。

架構師小祕圈

彙集20萬架構師的小圈子

 

相關文章
相關標籤/搜索