五分鐘告訴你Zookeeper在何時出生的!

誕生背景

  • 一個應用中多個獨立的程序如何更好地協同工作?如何讓開發人員更多的關注應用本身的邏輯?

  • 分散式系統集群中,如何有效合理的處理集群中失敗情況?

  • 可以將zookeeper理解為一個高可用的文件系統(但是沒有文件和文件夾的概念,只有一個叫做Znode的節點的概念)。

  • 由yahoo開發,用於分散式一致性處理的框架。

  • Zookeeper最早是Hadoop的一個子項目,主要為Hadoop生態系統中一些列組件提供統一的分散式協作服務

  • Zoo+Keeper,動物園看守者

可以做什麼

  • 從設計模式角度來看,是一個基於觀察者模式設計的分散式服務協調框架

  • 它負責存儲和管理大家都關心的數據,然後接受觀察者的註冊,一旦這些數據的狀態發生變化,Zookeeper就將負責通知已經在Zookeeper上註冊的那些觀察者做出相應的反應,從而實現集群中類似 Master/Slave的管理模式。

    Advertisements

  • 它是一個高性能的分散式數據一致性解決方案。

  • 著名的kafka,hadoop,dubbo都是基於zk搭建的。工作中遇到的Vitamin,Dubbo等

特點

  • 最終一致性。保證為所有客戶端展示同一個視圖,這是zookeeper裡面一個非常重要的功能。

  • 可靠性。如果消息被到一台伺服器接受,那麼它將被所有的伺服器接受。

  • 實時性。Zookeeper不能保證兩個客戶端能同時得到剛更新的數據,如果需要最新數據,應該在讀數據之前調用sync()介面。

  • 獨立性。各個Client之間互不干預。

  • 原子性。更新只能成功或者失敗,沒有中間狀態。

  • 順序性。所有Server,同一消息發布順序一致。

核心概念

網路結構

一個leader+多個follower。

Advertisements

  • Zookeeper啟動時,將從實例中選舉一個Leader

  • Leader和各個follower是互相通信的,對於Zookeeper系統的數據都是保存在內存裡面的,同樣也會備份一份在磁碟上。

  • Leader負責處理數據更新等操作

  • 如果Leader掛了,Zookeeper集群會重新選舉,在毫秒級別就會重新選舉出一個Leader。

  • 集群中除非有一半以上的Zookeeper節點掛了,Zookeeper Service才不可用。

集群角色

Leader:集群內部各伺服器的調度者,事務請求的唯一調度和處理者,保證集群事務處理的順序性。負責投票的發起和決議,更新系統狀態,處理寫請求。

Follower:參與客戶端非事務請求,轉發事務請求給Leader伺服器,參與請求的Proposal投票,參與Leader選舉投票。

Observer:不參與Leader投票,其他和Follower幾乎一樣

Client:執行讀寫請求的發起方

為什麼引入Observer?

  • Zookeeper需保證高可用和強一致性,為了支持更多的客戶端,需要增加更多Server;

  • 隨著Server增多,投票階段延遲增大,影響性能,權衡伸縮性和高吞吐率,引入Observer

  • Observer不參與投票,Observers接受客戶端的連接,並將寫請求轉發給leader節點;

  • 加入更多Observer節點,提高伸縮性,同時不影響吞吐率。

和CAP關係

一致性

所謂的一致性,實際上就是圍繞著「看見」來的。誰能看見?能否看見?什麼時候看見?舉個例子:淘寶後台賣家,在後台上架一件大促的商品,通過伺服器A提交到主資料庫,假設剛提交后立馬就有用戶去通過應用伺服器B去從資料庫查詢該商品,就會出現一個現象,賣家已經更新成功了,然而買家卻看不到;而經過一段時間后,主資料庫的數據同步到了從資料庫,買家就能查到了。

假設賣家更新成功之後買家立馬就能看到賣家的更新,則稱為強一致性;

如果賣家更新成功后買家不能看到賣家更新的內容,則稱為弱一致性;

而賣家更新成功后,買家經過一段時間最終能看到賣家的更新,則稱為最終一致性。

如果要給一致性下個定義,可以是分散式系統中狀態或數據保持同步和一致。特別需要注意一致性跟事務的區別,可以記得學習資料庫時特彆強調ACID,故而滿足ACID的資料庫能夠做事務,其中C即是一致性,因此,事務是一致性的一種特例,比起一致性更難達成。

如何保證在分散式環境下數據的最終一致,這個就是zookeeper需要解決的問題。

  • ZooKeeper是個CP(一致性+分區容錯性)的,即任何時刻對ZooKeeper的訪問請求能得到一致的數據結果,同時系統對網路分割具備容錯性;

  • 它不能保證每次服務請求的可用性。也就是在極端環境下,ZooKeeper可能會丟棄一些請求,消費者程序需要重新請求才能獲得結果。

數據類型

Zookeeper提供基於類似於文件系統的目錄節點樹方式的數據存儲,但是Zookeeper並不是用來專門存儲數據的,它的作用主要是用來維護和監控你存儲的數據的狀態變化。通過監控這些數據狀態的變化,從而可以達到基於數據的集群管理。

與Linux文件系統不同的是,Linux文件系統有目錄和文件的區別,而Zookeeper的數據節點稱為ZNode,ZNode是Zookeeper中數據的最小單元,每個ZNode都可以保存數據,同時還可以掛載子節點,因此構成了一個層次化的命名空間,稱為樹。

節點概念

  • 節點三部分數據:stat,data,children

  • ZNode是一棵樹,通過/分割路徑,例如/ab/path1

  • 每個ZNode都會保存自己的數據內容,還有另外一系列的屬性信息,如下圖,特別注意,保存了子節點的事務ID。

  • zxid是一個64為的數字,它高32位是epoch用來標識leader關係是否改變,每次一個leader被選出來,它都會有一個 新的epoch。低32位是個遞增計數。

  • version。通過版本號實現基於樂觀鎖的併發控制。若版本為-1,則忽略樂觀鎖。

節點類型

節點類型在創建時候確定,並且不能修改。

  • 持久化Znode節點:一旦創建,這個ZNode點存儲的數據不會主動消失,除非客戶端主動的delete。

  • 臨時ZNode節點:CLient連接到Zookeeper service的時候,會簡歷一個Session,之後用這個Zookeeper連接實例創建該類型的ZNode,一旦Client關閉了Zookeeper的連接,伺服器就會清楚Session,這個Session簡歷的ZNode節點都會從命名空間消失。也就是說,這個類型的ZNode的生命周期和Client建立的連接是一樣的。

  • 順序自動編號的ZNode節點:這種ZNode節點會根據當前已經存在的ZNode節點編號自動加1,而且不會隨Session斷開而消失。

  • 臨時自動編號節點。ZNode節點編號會自動增加,但會隨Session消失兒消失。

Watcher概念

  • 事件監聽器

  • 註冊只能保證一次消費

  • 客戶端串列執行

  • 輕量級設計

Watcher數據變更通知

Zookeeper使用Watcher機制實現分散式數據的發布/訂閱功能。

Zookeeper的Watcher機制主要包括客戶端線程、客戶端WatcherManager、Zookeeper伺服器三部分。客戶端在向Zookeeper伺服器註冊的同時,會將Watcher對象存儲在客戶端的WatcherManager當中。當Zookeeper伺服器觸發Watcher事件后,會向客戶端發送通知,客戶端線程從WatcherManager中取出對應的Watcher對象來執行回調邏輯。

原理分析

數據更新

  • 寫數據,要走Leader。一個客戶端進行寫數據請求時,如果是follower接收到寫請求,就會把請求轉發給Leader,Leader通過內部的Zab協議進行原子廣播,直到所有節點成功寫了數據后(內存同步以及磁碟更新),這次寫請求算是完成,然後Zookeeper Service就會給Client發迴響應。

  • 讀數據,不走Leader。因為集群中所有的Zookeeper節點都呈現一個同樣的命名空間視圖(就是結構數據),上面的寫請求已經保證了寫一次數據必須保證集群所有的Zookeeper節點都是同步命名空間的,所以讀的時候可以在任意一台Zookeeper節點上。

工作原理

Zab協議

Zookeeper的核心是廣播,這個機制保證了各個Server之間的同步。實現這個機制的協議叫做Zab協議。Zab(ZooKeeper Atomic Broadcast)原子消息廣播協議作為數據一致性的核心演算法,Zab協議是專為Zookeeper設計的支持崩潰恢復原子消息廣播演算法。

Zab協議核心如下:

所有的事務請求必須由一個全局唯一的伺服器(Leader)來協調處理,集群其餘的伺服器稱為follower伺服器。Leader伺服器負責將一個客戶端請求轉化為事務提議(Proposal),並將該proposal分發給集群所有的follower伺服器。之後Leader伺服器需要等待所有的follower伺服器的反饋,一旦超過了半數的follower伺服器進行了正確反饋后,那麼Leader伺服器就會再次向所有的follower伺服器分發commit消息,要求其將前一個proposal進行提交。

Zab模式

Zab協議包括兩種基本的模式:崩潰恢復和消息廣播。

  • 當整個服務框架啟動過程中或Leader伺服器出現網路中斷、崩潰退出與重啟等異常情況時,Zab協議就會進入恢復模式並選舉產生新的Leader伺服器。

  • 當選舉產生了新的Leader伺服器,同時集群中已經有過半的機器與該Leader伺服器完成了狀態同步之後,Zab協議就會退出恢復模式,狀態同步是指數據同步,用來保證集群在過半的機器能夠和Leader伺服器的數據狀態保持一致。

  • 當集群中已經有過半的Follower伺服器完成了和Leader伺服器的狀態同步,那麼整個服務框架就可以進入消息廣播模式。

  • 當一台同樣遵守Zab協議的伺服器啟動后加入到集群中,如果此時集群中已經存在一個Leader伺服器在負責進行消息廣播,那麼加入的伺服器就會自覺地進入數據恢復模式:找到Leader所在的伺服器,並與其進行數據同步,然後一起參與到消息廣播流程中去。

消息廣播

Zab協議的消息廣播過程使用是一個原子廣播協議,類似一個2PC提交過程。

崩潰恢復

恢復模式需要重新選舉出一個新的Leader,讓所有的Server都恢復到一個正確的狀態。

Master選主

選主過程

當Zookeeper集群中的一台伺服器出現以下兩種情況之一時,需要進入Leader選舉:

伺服器初始化啟動。

伺服器運行期間無法和Leader保持連接。

Master選舉原理

選主原理介紹:zookeeper的節點有兩種類型,持久節點跟臨時節點。臨時節點有個特性,就是如果註冊這個節點的機器失去連接(通常是宕機),那麼這個節點會被zookeeper刪除。選主過程就是利用這個特性,在伺服器啟動的時候,去zookeeper特定的一個目錄下註冊一個臨時節點(這個節點作為master,誰註冊了這個節點誰就是master),註冊的時候,如果發現該節點已經存在,則說明已經有別的伺服器註冊了(也就是有別的伺服器已經搶主成功),那麼當前伺服器只能放棄搶主,作為從機存在。同時,搶主失敗的當前伺服器需要訂閱該臨時節點的刪除事件,以便該節點刪除時(也就是註冊該節點的伺服器宕機了或者網路斷了之類的)進行再次搶主操作。從機具體需要去哪裡註冊伺服器列表的臨時節點,節點保存什麼信息,根據具體的業務不同自行約定。選主的過程,其實就是簡單的爭搶在zookeeper註冊臨時節點的操作,誰註冊了約定的臨時節點,誰就是master。

Master選舉過程

Zookeeper在3.4.0版本后只保留了TCP版本的 FastLeaderElection 選舉演算法。當一台機器進入Leader選舉時,當前集群可能會處於以下兩種狀態:

  • 集群中已存在Leader。

  • 集群中不存在Leader。

對於集群中已經存在Leader而言,此種情況一般都是某台機器啟動得較晚,在其啟動之前,集群已經在正常工作,對這種情況,該機器試圖去選舉Leader時,會被告知當前伺服器的Leader信息,對於該機器而言,僅僅需要和Leader機器建立起連接,並進行狀態同步即可。

而在集群中不存在Leader情況下則會相對複雜,其步驟如下:

(1) 第一次投票。無論哪種導致進行Leader選舉,集群的所有機器都處於試圖選舉出一個Leader的狀態,即LOOKING狀態,LOOKING機器會向所有其他機器發送消息,該消息稱為投票。投票中包含了SID(伺服器的唯一標識)和ZXID(事務ID),(SID, ZXID)形式來標識一次投票信息。假定Zookeeper由5台機器組成,SID分別為1、2、3、4、5,ZXID分別為9、9、9、8、8,並且此時SID為2的機器是Leader機器,某一時刻,1、2所在機器出現故障,因此集群開始進行Leader選舉。在第一次投票時,每台機器都會將自己作為投票對象,於是SID為3、4、5的機器投票情況分別為(3, 9),(4, 8), (5, 8)。

(2) 變更投票。每台機器發出投票后,也會收到其他機器的投票,每台機器會根據一定規則來處理收到的其他機器的投票,並以此來決定是否需要變更自己的投票,這個規則也是整個Leader選舉演算法的核心所在,其中術語描述如下

  • vote_sid:接收到的投票中所推舉Leader伺服器的SID。

  • vote_zxid:接收到的投票中所推舉Leader伺服器的ZXID。

  • self_sid:當前伺服器自己的SID。

  • self_zxid:當前伺服器自己的ZXID。

每次對收到的投票的處理,都是對(vote_sid, vote_zxid)和(self_sid, self_zxid)對比的過程。

  • 規則一:如果vote_zxid大於self_zxid,就認可當前收到的投票,並再次將該投票發送出去。

  • 規則二:如果vote_zxid小於self_zxid,那麼堅持自己的投票,不做任何變更。

  • 規則三:如果vote_zxid等於self_zxid,那麼就對比兩者的SID,如果vote_sid大於self_sid,那麼就認可當前收到的投票,並再次將該投票發送出去。

  • 規則四:如果vote_zxid等於self_zxid,並且vote_sid小於self_sid,那麼堅持自己的投票,不做任何變更。

  • 結合上面規則,給出下面的集群變更過程。

(3) 確定Leader。經過第二輪投票后,集群中的每台機器都會再次接收到其他機器的投票,然後開始統計投票,如果一台機器收到了超過半數的相同投票,那麼這個投票對應的SID機器即為Leader。此時Server3將成為Leader。

由上面規則可知,通常那台伺服器上的數據越新(ZXID會越大),其成為Leader的可能性越大,也就越能夠保證數據的恢復。如果ZXID相同,則SID越大機會越大。

應用場景

Zookeeper常用的場景

  • 集群管理

  • 統一命名服務

  • 配置管理

  • 分散式鎖

  • 分散式隊列

集群管理

  • 分散式環境中,實時掌握每個節點的狀態是必要的;

  • 可將節點信息寫入Zookeeper的一個znode上;

  • 監聽這個znode可獲取它的實時狀態變化

在 Zookeeper 上創建一個 EPHEMERAL(臨時節點) 類型的目錄節點,然後每個 Server 在它們創建目錄節點的父目錄節點上調用 getChildren(String path, boolean watch) 方法並設置 watch 為 true,由於是 EPHEMERAL 目錄節點,當創建它的 Server 死去,這個目錄節點也隨之被刪除,所以 Children 將會變化,這時 getChildren上的 Watch 將會被調用,所以其它 Server 就知道已經有某台 Server 死去了。新增 Server 也是同樣的原理。

如何在集群中選出一個 Master Server? 和前面的一樣每台 Server 創建一個 EPHEMERAL 目錄節點,不同的是它還是一個 SEQUENTIAL 目錄節點,所以它是個 EPHEMERAL_SEQUENTIAL 目錄節點。之所以它是 EPHEMERAL_SEQUENTIAL 目錄節點,是因為我們可以給每台 Server 編號,我們可以選擇當前是最小編號的 Server 為 Master,假如這個最小編號的 Server 死去,由於是 EPHEMERAL 節點,死去的 Server 對應的節點也被刪除,所以當前的節點列表中又出現一個最小編號的節點,我們就選擇這個節點為當前 Master。這樣就實現了動態選擇 Master,避免了傳統意義上單 Master 容易出現單點故障的問題。

統一命名服務

  • 分散式環境下,經常需要對應用/服務進行統一命名,便於識別不同服務,類似於域名與ip之間對應關係,通過名稱來獲取資源或服務的地址,提供者等信息

  • 按照層次結構組織服務/應用名稱的方式命名,將服務名稱以及地址信息寫到Zookeeper上。

  • 客戶端通過Zookeeper獲取可用服務列表類。

配置管理

  • 分散式環境下,配置文件管理和同步是一個常見問題;

  • 對配置文件修改後,希望能夠快速同步到各個節點上

  • 一個集群中,所有節點的配置信息是一致的,比如Hadoop;

  • 可將配置信息寫入Zookeeper的一個znode上,各個節點監聽這個znode。

  • 一旦znode中的數據被修改,zookeeper將通知各個節點

分散式鎖

  • Zookeeper是強一致的。多個客戶端同時在Zookeeper上創建相同znode,只有一個創建成功。

  • 多個客戶端同時在Zookeeper上創建相同znode ,創建成功的那個客戶端得到鎖,其他客戶端等待。

  • 各個客戶端在某個znode下創建臨時znode (類型為CreateMode.EPHEMERAL_SEQUENTIAL),這樣,該znode可掌握全局訪問時序。

獲得鎖的Server創建一個 EPHEMERAL_SEQUENTIAL 目錄節點,然後調用getChildren方法獲取當前的目錄節點列表中最小的目錄節點是不是就是自己創建的目錄節點,如果正是自己創建的,那麼它就獲得了這個鎖,如果不是那麼它就調用 exists(String path, boolean watch) 方法並監控 Zookeeper 上目錄節點列表的變化,一直到自己創建的節點是列表中最小編號的目錄節點,從而獲得鎖,釋放鎖很簡單,只要刪除前面它自己所創建的目錄節點就行了。

分散式隊列

  • 隊列按照 FIFO 方式進行入隊和出隊操作,例如實現生產者和消費者模型。(可通過分散式鎖實現)

  • 同步隊列:當一個隊列的成員都聚齊時,這個隊列才可用,否則一直等待所有成員到達,這種是同步隊列。

  • 一個job由多個task組成,只有所有任務完成後,job才運行完成。可為job創建一個/job目錄,然後在該目錄下,為每個完成的task創建一個臨時znode,一旦臨時節點數目達到task總數,則job運行完成。

Zookeeper在Proxy中的應用舉例

問題

為什麼zookeeper集群中節點配置個數是奇數個?

我們都知道,Zookeeper要安裝在奇數個節點,但是為什麼呢?

Zookeeper的大部分操作都是通過選舉產生的。比如,標記一個寫是否成功是要在唱過一半節點發送寫操作請求成功時才認為有效。同樣,Zookeeper選舉Leader節點也要超過一半節點同意才有效。最後,Zookeeper手正常是要根據是否超過一半的節點正常才算正常。這是基於CAP的一致性原理。

Zookeeper有這樣一個特性:集群中只要有過半的機器正常工作的,那麼整個集群對外就是可用的。也就是說如果有2個Zookeeper,那麼只要1個死了,Zookeeper就掛了,不能用了。但是,如果有3個,一個掛了,還有2個,過半了,服務還是正常的。

Advertisements

你可能會喜歡