ZooKeeper(一)基礎

引言

因爲一臺服務器的處理能力是有限的,在大用戶量和高併發的狀況下,一般須要不少臺服務器同時工做對外提供服務。這麼多機器同時工做,怎麼來管理這些服務器呢?好比某臺服務器宕機了,就要確保請求再也不發送到這臺服務器;某個程序的配置修改了,多臺服務器上的配置要做相應修改;或者多個服務必須按照特定的順序執行;若是多個服務是有事務的,若是中間某個服務失敗了,那這多個服務都必須回滾。對於這些狀況最好是有一個專門的管理中心來管理,而ZooKeeper就能夠做爲這個管理中心。node

ZooKeeper是什麼

ZooKeeper是Apache下的一個Java開源項目(最初由Yahoo開發,後捐獻給了Apache)。
ZooKeeper的原始功能很簡單,基於它的層次型的目錄樹的數據結構,並經過對樹上的節點進行有效管理,能夠設計出各類各樣的分佈式集羣管理功能。此外,ZooKeeper自己也是分佈式的。算法

ZooKeeper的數據模型

Zookeeper 會維護一個具備層次關係的樹狀的數據結構,它很是相似於一個標準的文件系統,以下圖所示:
數據庫

ZooKeeper樹狀結構中的每個節點稱做——<font color=#D2691E>Znode</font>。每個節點都有一個名稱,且以 / 開頭,其中最頂層的節點是 / ,最終的狀況就是每個節點在樹狀結構中,會有一個相似絕對路徑的惟一標識,如上圖中的 Server1 這個Znode 的標識爲 /NameService/Server1服務器

Znode的結構

雖然ZooKeeper的樹狀結構相似文件系統,可是Znode兼有文件和目錄的特色,一個Znode既能在它下面建立子節點,做爲路徑標識的一部分,同時這個節點同時也能存儲數據,但這個存儲不是設計用來做常規的數據庫存儲,而主要存放分佈式應用的配置信息、狀態信息等,這些數據的共同特性就是它們都是很小的數據,一般以KB爲單位。網絡

ZooKeeper數據模型中的每一個Znode都維護着一個 <font color=#D2691E>stat</font> 結構。
一個stat僅提供一個Znode的元數據。它由版本號,操做控制列表(ACL),時間戳和數據長度組成。session

  • 版本號 - 每一個Znode都有版本號,這意味着每當與Znode相關聯的數據發生變化時,其對應的版本號也會增長。當多個ZooKeeper客戶端嘗試在同一Znode上執行操做時,版本號的使用就很重要。數據結構

    • version : 當前節點內容(數據)的版本號
    • cversion : 當前節點子節點的版本號
    • aversion : 當前節點ACL的版本號
  • 操做控制列表(ACL) - ACL基本上是訪問Znode的認證機制。它管理全部Znode讀取和寫入操做。每個節點都擁有本身的ACL,這個列表規定了用戶的權限,即限定了特定用戶對目標節點能夠執行的操做。權限的種類有:併發

    • CREATE : 建立子節點的權限
    • READ : 獲取節點數據和節點列表的權限
    • WRITE : 更新節點數據的權限
    • DELETE : 刪除子節點的權限
    • ADMIN : 設置節點ACL的權限
  • 時間戳 - 導致ZooKeeper節點狀態改變的每個操做都將使節點接收到一個Zxid格式的時間戳,而且這個時間戳全局有序。也就是說,每一個對節點的改變都將產生一個惟一的Zxid。若是Zxid1的值小於Zxid2的值,那麼Zxid1所對應的事件發生在Zxid2所對應的事件以前。實際上,ZooKeeper的每一個節點維護者三個Zxid值,爲別爲:cZxid、mZxid、pZxid。app

    • cZxid: 是節點的建立時間所對應的Zxid格式時間戳。
    • mZxid:是節點的修改時間所對應的Zxid格式時間戳。
  • 數據長度 - 存儲在znode中的數據總量是數據長度。最多能夠存儲1MB的數據。

每一個Znode由3部分組成:異步

  1. stat:此爲狀態信息, 描述該Znode的版本, 權限等信息
  2. data:與該Znode關聯的數據
  3. children:該Znode下的子節點

節點類型

ZooKeeper中的節點有兩種,分別爲臨時(ephemeral)節點和永久(persistent)節點。節點的類型在建立時即被肯定,而且不能改變。

  • <font color=#D2691E>臨時節點</font>:該節點的生命週期依賴於建立它們的會話。一旦會話(Session)結束,臨時節點將被自動刪除,固然能夠也能夠手動刪除。雖然每一個臨時的Znode都會綁定到一個客戶端會話,但他們對全部的客戶端仍是可見的。另外,ZooKeeper的臨時節點不容許擁有子節點。<br/>
  • <font color=#D2691E>永久節點</font>:該節點的生命週期不依賴於會話,客戶端建立一個永久節點後即便斷開鏈接,改節點仍然存在,而且只有在客戶端顯式執行刪除操做後,永久節點才被刪除。默認建立的節點都是永久節點

順序節點

<font color=#D2691E>順序節點</font>能夠是持久的或臨時的。當一個新的Znode被建立爲一個順序節點時,ZooKeeper經過將10位的序列號附加到原始名稱來設置Znode的路徑。例如,若是將具備路徑/myapp的Znode建立爲順序節點,則ZooKeeper會將路徑更改成/myapp0000000001,並將下一個序列號設置爲0000000002,這個序列號由父節點維護。若是兩個順序節點是同時建立的,那麼ZooKeeper不會對每一個Znode使用相同的數字。順序節點在鎖定和同步中起重要做用,順序號能夠被用於爲全部的事件進行全局排序,這樣客戶端能夠經過順序號推斷事件的順序。

綜合上面兩節內容,ZooKeeper有四種形式的節點:

  • PERSISTENT(永久節點)
  • PERSISTENT_SEQUENTIAL(永久有序節點)
  • EPHEMERAL(臨時節點)
  • EPHEMERAL_SEQUENTIAL(臨時有序節點)

Znode的屬性

一個節點自身擁有表示其狀態的許多重要屬性,以下圖所示。
Znode的屬性

ZooKeeper Session(會話)

Zookeeper 的客戶端和服務器通訊採用長鏈接方式,每一個客戶端和服務器經過心跳來保持鏈接,這個鏈接狀態稱爲 Session。

會話對於ZooKeeper的操做很是重要。會話中的請求按FIFO順序執行。一旦客戶端鏈接到服務器,將創建會話並向客戶端分配會話ID 。

客戶端以特定的時間間隔發送心跳以保持會話有效。若是ZooKeeper集羣在超過指定的時間都沒有從客戶端接收到心跳,則會話會被認爲結束(會話超時)。會話超時一般以毫秒爲單位。

Client和Zookeeper集羣創建鏈接,整個session狀態變化如圖所示:


若是Client由於Timeout和Zookeeper Server失去鏈接,client處在CONNECTING狀態,會自動嘗試再去鏈接Server,若是在session有效期內再次成功鏈接到某個Server,則回到CONNECTED狀態。

注意:若是由於網絡狀態很差,client和Server失去聯繫,client會停留在當前狀態,會嘗試主動再次鏈接Zookeeper Server。client不能宣稱本身的session expired,session expired是由Zookeeper Server來決定的,client能夠選擇本身主動關閉session。

ZooKeeper Watch(監聽)

Zookeeper watch是一種監聽通知機制。客戶端註冊監聽它關心的目錄節點,當目錄節點發生變化(數據改變、被刪除、子目錄節點增長刪除)時,zookeeper會通知客戶端。,監視事件能夠理解爲一次性的觸發器。官方定義以下:

a watch event is one-time trigger, sent to the client that set the watch, which occurs when the data for which the watch was set changes。

Watch的關鍵點:

  • (一次性觸發)One-time trigger

當設置監視的數據發生改變時,該監視事件會被髮送到客戶端,例如,若是客戶端調用了getData("/znode1", true) 而且稍後 /znode1 節點上的數據發生了改變或者被刪除了,客戶端將會獲取到 /znode1 發生變化的監視事件,而若是 /znode1 再一次發生了變化,除非客戶端再次對/znode1 設置監視,不然客戶端不會收到事件通知。

  • (發送至客戶端)Sent to the client

Zookeeper客戶端和服務端是經過 socket 進行通訊的,因爲網絡存在故障,因此監視事件頗有可能不會成功地到達客戶端,監視事件是異步發送至監視者的,Zookeeper 自己提供了順序保證(ordering guarantee):即客戶端只有首先看到了監視事件後,纔會感知到它所設置監視的znode發生了變化(a client will never see a change for which it has set a watch until it first sees the watch event)。網絡延遲或者其餘因素可能致使不一樣的客戶端在不一樣的時刻感知某一監視事件,可是不一樣的客戶端所看到的一切具備一致的順序(有序一致性)。

ZooKeeper能夠爲全部的讀操做設置watch,這些讀操做包括:exists()、getChildren()及getData()。

ZooKeeper所管理的watch能夠分爲兩類:

  • 數據watch(data watches):getData和exists負責設置數據watch
  • 孩子watch(child watches):getChildren負責設置孩子watch

咱們能夠經過操做返回的數據來設置不一樣的watch:

  • getData和exists:返回關於節點的數據信息
  • getChildren:返回孩子列表

所以

  • 一個成功的setData操做將觸發Znode的數據watch
  • 一個成功的create操做將觸發Znode的數據watch以及孩子watch
  • 一個成功的delete操做將觸發Znode的數據watch以及孩子watch

watch註冊與處觸發

  • exists操做上的watch,在被監視的Znode建立、刪除或數據更新時被觸發。
  • getData操做上的watch,在被監視的Znode刪除或數據更新時被觸發。在被建立時不能被觸發,由於只有Znode必定存在,getData操做纔會成功。
  • getChildren操做上的watch,在被監視的Znode的子節點建立或刪除,或是這個Znode自身被刪除時被觸發。能夠經過查看watch事件類型來區分是Znode,仍是他的子節點被刪除:NodeDelete表示Znode被刪除,NodeDeletedChanged表示子節點被刪除。

Zookeeper 中的監視是輕量級的,所以容易設置、維護和分發。當客戶端與 Zookeeper 服務器失去聯繫時,客戶端並不會收到監視事件的通知,只有當客戶端從新鏈接後,若在必要的狀況下,之前註冊的監視會從新被註冊並觸發,對於開發人員來講這一般是透明的。只有一種狀況會致使監視事件的丟失,即:經過exists()設置了某個znode節點的監視,可是若是某個客戶端在此znode節點被建立和刪除的時間間隔內與zookeeper服務器失去了聯繫,該客戶端即便稍後從新鏈接 zookeeper服務器後也得不到事件通知。

咱們使用ZooKeeper,簡單地理解就是使用ZooKeeper的<font color=#D2691E>文件系統+通知機制</font>

ZooKeeper集羣

Zookeeper服務自身組成一個集羣(2n+1個服務容許n個失效)。在Zookeeper集羣中,主要分爲三者角色,而每個節點同時只能扮演一種角色,這三種角色分別是:

  • <font color=#D2691E>Leader</font>:

    • 事務請求的惟一調度和處理者,保證集羣事務處理的順序性
    • 集羣內各服務器的調度者 leader會與每一個follower和observer創建一個tcp長鏈接,而且爲每一個follower和observer創建一個learnerhandler,進行數據同步,請求轉發和proposal投票等功能。
  • <font color=#D2691E>Follower</font>:

    • 處理客戶端的非事務請求,轉發事務請求給leader
    • 參與事務請求Proposal投票
    • 參與leader選舉投票
    • 判斷當前請求是否爲事務請求,如果則轉發給leader完成事務日誌記錄後,向leader發送ack信息
  • <font color=#D2691E>Observer</font>:

    • 與Leader進行數據交換(同步)
    • 能夠接收客戶端鏈接,將寫請求轉發給Leader節點
    • Observer不參與投票過程,只同步Leader的狀態。
Propsal投票:每個事務都須要集羣中超過半數的機器投票承認才能被真正地應用到ZK的內存數據庫中。

下圖描述了 ZooKeeper集羣「客戶端-服務端」的結構

ZooKeeper集羣的結構

ZooKeeper的一致性特色

Zookeeper提供的一致性是弱一致性,數據的同步有以下規則:ZooKeeper確保對znode樹的每個修改都會被同步到集羣中超過半數的機器上,那麼就認爲更新成功。因此就有可能有節點的數據不是最新的而被客戶端訪問到。而且會有一個時間點,數據在集羣中是不一致的.也就是Zookeeper只保證最終一致性,可是實時的一致性能夠由客戶端調用本身來保證,經過調用sync()方法

  • 單一視圖性(Single System Image):client不論鏈接到哪一個server,看到的數據是同樣的。
  • 實時性:僞實時性。Zookeeper保證客戶端將在一個時間間隔範圍內(集羣大的時候可能要十幾秒)從server得到的信息是實時的。但因爲網絡延時等緣由,Zookeeper不能保證兩個客戶端能同時獲得剛更新的數據,若是須要最新數據,應該在讀數據以前調用sync()接口。
  • 原子性(Atomicity):操做要麼所有成功,要麼所有失敗,沒有中間狀態。
  • 順序性:客戶端的更新順序與它們被髮送的順序相一致。具體表現爲全局有序和偏序兩種:全局有序是指若是在一臺服務器上消息a在消息b前發佈,則在全部Server上消息a都將在消息b前被髮布;偏序是指若是一個消息b在消息a後被同一個發送者發佈,a必將排在b前面。
  • 可靠性:一旦一個更新操做被應用,那麼在客戶端再次更新它以前,它的值將不會改變。這個保證將會產生下面兩種結果:

    1. 若是客戶端成功地得到了正確的返回代碼,那麼說明更新已經成果。若是不可以得到返回代碼(因爲通訊錯誤、超時等等),那麼客戶端將不知道更新操做是否生效。
    2. 當從故障恢復的時候,任何client看到的已經執行成功的更新操做將不會被回滾。

有了這些一致性保證, ZooKeeper 更高級功能的設計與實現將會變得很是容易,例如: leader 選舉、隊列以及可撤銷鎖等機制的實現。

用分佈式系統的CAP原則來分析ZooKeeper.

  1. C: ZooKeeper保證了最終一致性,在十幾秒能夠sync到各個節點.
  2. A: ZooKeeper保證了可用性,數據老是可用的,沒有鎖.而且有一大半的節點所擁有的數據是最新的,實時的. 若是想保證取得是數據必定是最新的,須要手工調用sync()
  3. P: 有2點須要分析的.

    • 節點多了會致使寫數據延時很是大,由於須要多個節點同步.
    • 節點多了leader選舉很是耗時, 就會放大網絡的問題. 能夠經過引入observer節點緩解這個問題.

ZooKeeper的工做原理

在ZooKeeper的集羣中,各個節點共有下面3種角色和4種狀態:

  • 角色:leader,follower,observer
  • 狀態:leading,following,observing,looking

4種狀態的解釋:

  • LOOKING:當前server不知道leader是誰,正在搜尋。
  • LEADING:當前server即爲選舉出來的leader。
  • FOLLOWING:leader已經選舉出來,當前server與之同步。
  • OBSERVING:observer的行爲在大多數狀況下與follower徹底一致,可是他們不參加選舉和投票,而僅僅接受(observing)選舉和投票的結果。

ZooKeeper的核心是原子廣播,這個機制保證了各個server之間的同步。實現這個機制的協議叫作Zab協議(ZooKeeper Atomic Broadcast protocol)。Zab協議有兩種模式,它們分別是恢復模式(Recovery選主)和廣播模式(Broadcast同步)。
當服務啓動或者在leader崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數server完成了和leader的狀態同步之後,恢復模式就結束了。狀態同步保證了leader和Server具備相同的系統狀態。

爲了保證事務的順序一致性,ZooKeeper採用了遞增的事務id號(zxid)來標識事務。全部的提議(proposal)都在被提出的時候加上了zxid。實現中zxid是一個64位的數字,它高32位是epoch用來標識leader關係是否改變,每次一個leader被選出來,它都會有一個新的epoch(每leader選舉一次+1),標識當前屬於那個leader的統治時期。低32位爲事務操做次數(每增長一次事務+1)

關於Leader Election

當leader崩潰或者集羣啓動,這時候zk進入恢復模式,恢復模式須要從新選舉出一個新的leader,讓全部的Server都恢復到一個正確的狀態。Zk的選舉算法有兩種:一種是基於basic paxos實現的,另一種是基於fast paxos算法實現的。系統默認的選舉算法爲fast paxos。先介紹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會記錄事務日誌並按期進行快照,方便在恢復時進行狀態恢復。選主的具體流程圖以下所示:
basic paxos流程

fast paxos流程是在選舉過程當中,某Server首先向全部Server提議本身要成爲leader,當其它Server收到提議之後,解決epoch和zxid的衝突,並接受對方的提議,而後向對方發送接受提議完成的消息,重複這個流程,最後必定能選舉出Leader。其流程以下所示:
fast paxos流程

相關文章
相關標籤/搜索