深刻了解zookeeper(三)

1、ZooKeeper 的實現

1.1 ZooKeeper處理單點故障

咱們知道能夠經過ZooKeeper對分佈式系統進行Master選舉,來解決分佈式系統的單點故障,如圖所示。node

那麼咱們繼續分析一下,ZooKeeper經過Master選舉來幫助分佈式系統解決單點故障, 保證該系統中每時每刻只有一個Master爲分佈式系統提供服務。也就是說分佈式的單點問題交給了ZooKeeper來處理,不知道你們此時有沒有發現一 個問題——"故障轉移到了ZooKeeper身上"。你們看一下圖就會發現,若是咱們的ZooKeeper只用一臺機器來提供服務,若這臺機器掛了,那麼 該分佈式系統就直接變成雙Master模式了,那麼咱們在分佈式系統中引入ZooKeeper也就失去了意義。那麼這也就意味着,ZooKeeper在其實現的過程中要作一些可用性和恢復性的保證。這樣才能讓咱們放心的以ZooKeeper爲起點來構建咱們的分佈式系統,來達到節省成本和減小bug的目的。算法

1.2 ZooKeeper運行模式

ZooKeeper服務有兩種不一樣的運行模式。一種是"獨立模式"(standalone mode),即只有一個ZooKeeper服務器。這種模式較爲簡單,比較適合於測試環境,甚至能夠在單元測試中採用,可是不能保證高可用性和恢復性。在生產環境中的ZooKeeper一般以"複製模式"(replicated mode)運行於一個計算機集羣上,這個計算機集羣被稱爲一個"集合體"(ensemble)。數據庫

 

ZooKeeper經過複製來實現高可用性,只要集合體中半數以上的機器處於可用狀態,它就可以提供服務。例如,在一個有5個節點的集合體中,每一個Follower節點的數據都是Leader節點數據的副本,也就是說咱們的每一個節點的數據視圖都是同樣的,這樣就能夠有五個節點提供ZooKeeper服務。而且集合體中任意2臺機器出現故障,均可以保證服務繼續,由於剩下的3臺機器超過了半數。服務器

注意,6個節點的集合體也只可以容忍2臺機器出現故障,由於若是3臺機器出現故障,剩下的3臺機器沒有超過集合體的半數。出於這個緣由,一個集合體一般包含奇數臺機器。

從概念上來講,ZooKeeper它所作的就是確保對Znode樹的每個修改都會被複制到集合體中超過半數的 機器上。若是少於半數的機器出現故障,則最少有一臺機器會保存最新的狀態,那麼這臺機器就是咱們的Leader。其他的副本最終也會更新到這個狀態。若是 Leader掛了,因爲其餘機器保存了Leader的副本,那就能夠從中選出一臺機器做爲新的Leader繼續提供服務。session

1.3 ZooKeeper的讀寫機制

(1) 概述app

ZooKeeper核心思想是,提供一個非鎖機制的Wait Free用於分佈式系統同步的核心服務。提供簡單的文件建立、讀寫操做接口,其系統核心自己對文件讀寫並不提供加鎖互斥的服務,可是提供基於版本比對的更新操做,客戶端能夠基於此本身實現加鎖邏輯。以下圖1.3所示。分佈式

 

(2) ZK集羣服務性能

 Zookeeper是一個由多個Server組成的集羣,該集羣有一個Leader,多個Follower。客戶端能夠鏈接任意ZooKeeper服務節點來讀寫數據,以下圖1.4所示。單元測試

 

ZK集羣中每一個Server,都保存一份數據副本。Zookeeper使用簡單的同步策略,經過如下兩條基本保證來實現數據的一致性:測試

① 全局串行化全部的寫操做

② 保證同一客戶端的指令被FIFO執行(以及消息通知的FIFO

全部的讀請求由Zk Server 本地響應,全部的更新請求將轉發給Leader,由Leader實施。

(3) ZK組件

ZK組件,如圖1.5所示。ZK組件除了請求處理器(Request Processor)之外,組成ZK服務的每個Server會複製這些組件的副本。 

 

ReplicatedDatabase是一個內存數據庫,它包含了整個Data Tree。爲了恢復,更新會被記錄到磁盤,而且寫在被應用到內存數據庫以前,先被序列化到磁盤。

每個ZK Server,可服務於多個Client。Client能夠鏈接到一臺Server,來提交請求。讀請求,由每臺Server數據庫的本地副原本進行服務。改變服務器的狀態的寫請求,須要經過一致性協議來處理。

做爲一致性協議的一部分,來自Client的全部寫請求,都要被轉發到一個單獨的Server,稱做Leader。ZK集羣中其餘Server 稱做Follower,負責接收Leader發來的提議消息,而且對消息轉發達成一致。消息層處理leader失效,同步Followers和Leader。

ZooKeeper使用自定義的原子性消息協議。因爲消息傳送層是原子性的,ZooKeeper可以保證本地副本不產生分歧。當leader收到一個寫請求,它會計算出當寫操做完成後系統將會是什麼狀態,接着將之轉變爲一個捕獲狀態的事務。

(4) ZK性能

ZooKeeper被應用程序普遍使用,並有數以千計 的客戶端同時的訪問它,因此咱們須要高吞吐量。咱們爲ZooKeeper 設計的工做負載的讀寫比例是 2:1以上。然而咱們發現,ZooKeeper的高寫入吞吐量,也容許它被用於一些寫佔主導的工做負載。ZooKeeper經過每臺Server上的本地 ZK的狀態副本,來提供高讀取吞吐量。所以,容錯性讀吞吐量是以添加到該服務的服務器數量爲尺度。寫吞吐量並不以添加到該服務的機器數量爲尺度。

例如,在它的誕生地Yahoo公司,對於寫佔主導的工做負載來講,ZooKeeper的基準吞吐量已經超過每秒10000個操做;對於常規的以讀爲主導的工做負載來講,吞吐量更是高出了好幾倍。

2、ZooKeeper的保證

通過上面的分析,咱們知道要保證ZooKeeper服務的高可用性就須要採用分佈式模式,來冗餘數據寫多份,寫多份帶來一致性問題,一致性問題又會帶來性能問題,那麼就此陷入了無解的死循環。那麼在這,就涉及到了咱們分佈式領域的著名的CAP理論,在這就簡單的給你們介紹一下,關於CAP的詳細內容你們能夠網上查閱。

2.1 CAP理論

(1) 理論概述

分佈式領域中存在CAP理論:

 C:Consistency,一致性,數據一致更新,全部數據變更都是同步的。

 A:Availability,可用性,系統具備好的響應性能。

 P:Partition tolerance,分區容錯性。以實際效果而言,分區至關於對通訊的時限要求。系統若是不能在時限內達成數據一致性,就意味着發生了分區的狀況,必須就當前操做在C和A之間作出選擇,也就是說不管任何消息丟失,系統均可用。

該理論已被證實:任何分佈式系統只可同時知足兩點,沒法三者兼顧。 所以,將精力浪費在思考如何設計能知足三者的完美系統上是愚鈍的,應該根據應用場景進行適當取捨。

(2) 一致性分類

一致性是指從系統外部讀取系統內部的數據時,在必定約束條件下相同,即數據變更在系統內部各節點應該是同步的。根據一致性的強弱程度不一樣,能夠將一致性級別分爲以下幾種:

① 強一致性(strong consistency)。任什麼時候刻,任何用戶都能讀取到最近一次成功更新的數據。

② 單調一致性(monotonic consistency)。任什麼時候刻,任何用戶一旦讀到某個數據在某次更新後的值,那麼就不會再讀到比這個值更舊的值。也就是說,可獲取的數據順序必是單調遞增的。

③ 會話一致性(session consistency)。任何用戶在某次會話中,一旦讀到某個數據在某次更新後的值,那麼在本次會話中就不會再讀到比這個值更舊的值。會話一致性是在單調一致性的基礎上進一步放鬆約束,只保證單個用戶單個會話內的單調性,在不一樣用戶或同一用戶不一樣會話間則沒有保障。

 最終一致性(eventual consistency)。用戶只能讀到某次更新後的值,但系統保證數據將最終達到徹底一致的狀態,只是所需時間不能保障。

⑤ 弱一致性(weak consistency)。用戶沒法在肯定時間內讀到最新更新的值。

2.2 ZooKeeper與CAP理論

咱們知道ZooKeeper也是一種分佈式系統,它在一致性上有人認爲它提供的是一種強一致性的服務(經過sync操做),也有人認爲是單調一致性(更新時的大多說概念),還有人爲是最終一致性(順序一致性),反正各有各的道理這裏就不在爭辯了。而後它在分區容錯性和可用性上作了必定折中,這和CAP理論是吻合的。ZooKeeper從如下幾點保證了數據的一致性

① 順序一致性

來自任意特定客戶端的更新都會按其發送順序被提交。也就是說,若是一個客戶端將Znode z的值更新爲a,在以後的操做中,它又將z的值更新爲b,則沒有客戶端可以在看到z的值是b以後再看到值a(若是沒有其餘對z的更新)。

② 原子性

每一個更新要麼成功,要麼失敗。這意味着若是一個更新失敗,則不會有客戶端會看到這個更新的結果。

③ 單一系統映像

一 個客戶端不管鏈接到哪一臺服務器,它看到的都是一樣的系統視圖。這意味着,若是一個客戶端在同一個會話中鏈接到一臺新的服務器,它所看到的系統狀態不會比 在以前服務器上所看到的更老。當一臺服務器出現故障,致使它的一個客戶端須要嘗試鏈接集合體中其餘的服務器時,全部滯後於故障服務器的服務器都不會接受該 鏈接請求,除非這些服務器遇上故障服務器。

④ 持久性

一個更新一旦成功,其結果就會持久存在而且不會被撤銷。這代表更新不會受到服務器故障的影響。

 

3、ZooKeeper原理

3.1 原理概述

Zookeeper的核心是原子廣播機制,這個機制保證了各個server之間的同步。實現這個機制的協議叫作Zab協議。Zab協議有兩種模式,它們分別是恢復模式廣播模式

(1) 恢復模式

當服務啓動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數server完成了和leader的狀態同步之後,恢復模式就結束了。狀態同步保證了leader和server具備相同的系統狀態。

(2) 廣播模式

一旦Leader已經和多數的Follower進行了狀態同步後,他就能夠開始廣播消息了,即進入廣播狀態。這時候當一個Server加入ZooKeeper服務中,它會在恢復模式下啓動,發現Leader,並和Leader進行狀態同步。待到同步結束,它也參與消息廣播。ZooKeeper服務一直維持在Broadcast狀態,直到Leader崩潰了或者Leader失去了大部分的Followers支持。

Broadcast模式極其相似於分佈式事務中的2pc(two-phrase commit 兩階段提交):即Leader提起一個決議,由Followers進行投票,Leader對投票結果進行計算決定是否經過該決議,若是經過執行該決議(事務),不然什麼也不作。

廣播模式ZooKeeper Server會接受Client請求,全部的寫請求都被轉發給領導者,再由領導者將更新廣播給跟隨者。當半數以上的跟隨者已經將修改持久化以後,領導者纔會提交這個更新,而後客戶端纔會收到一個更新成功的響應。這個用來達成共識的協議被設計成具備原子性,所以每一個修改要麼成功要麼失敗。

 

3.2 Zab協議詳解

3.2.1 廣播模式

廣播模式相似一個簡單的兩階段提交:Leader發起一個請求,收集選票,而且最終提交,圖3.3演示了咱們協議的消息流程。咱們能夠簡化該兩階段提交協議,由於咱們並無"aborts"的狀況。followers要麼確認Leader的Propose,要麼丟棄該Leader的Propose。沒有"aborts"意味着,只要有指定數量的機器確認了該Propose,而不是等待全部機器的迴應。

 

廣播協議在全部的通信過程當中使用TCP的FIFO信道,經過使用該信道,使保持有序性變得很是的容易。經過FIFO信道,消息被有序的deliver。只要收到的消息一被處理,其順序就會被保存下來。

Leader會廣播已經被deliver的Proposal消息。在發出一個Proposal消息前,Leader會分配給Proposal一個單調遞增的惟一id,稱之爲zxid。由於Zab保證了因果有序, 因此遞交的消息也會按照zxid進行排序。廣播是把Proposal封裝到消息當中,並添加到指向Follower的輸出隊列中,經過FIFO信道發送到 Follower。當Follower收到一個Proposal時,會將其寫入到磁盤,能夠的話進行批量寫入。一旦被寫入到磁盤媒介當 中,Follower就會發送一個ACK給Leader。 當Leader收到了指定數量的ACK時,Leader將廣播commit消息並在本地deliver該消息。當收到Leader發來commit消息 時,Follower也會遞交該消息。

須要注意的是, 該簡化的兩階段提交自身並不能解決Leader故障,因此咱們 添加恢復模式來解決Leader故障。

3.2.2 恢復模式

(1) 恢復階段概述

  正常工做時Zab協議會一直處於廣播模式,直到Leader故障或失去了指定數量的Followers。 爲了保證進度,恢復過程當中必須選舉出一個新Leader,而且最終讓全部的Server擁有一個正確的狀態。對於Leader選舉,須要一個可以成功高几 率的保證存活的算法。Leader選舉協議,不只可以讓一個Leader得知它是leader,而且有指定數量的Follower贊成該決定。若是 Leader選舉階段發生錯誤,那麼Servers將不會取得進展。最終會發生超時,從新進行Leader選舉。在咱們的實現中,Leader選舉有兩種不一樣的實現方式。若是有指定數量的Server正常運行,快速選舉的完成只須要幾百毫秒。

(2)恢復階段的保證

  該恢復過程的複雜部分是在一個給定的時間內,提議衝突的絕對數量。最大數量衝突提議是一個可配置的選項,可是默認是1000。爲了使該協議可以即便在Leader故障的狀況下也能正常運做。咱們須要作出兩條具體的保證:

 咱們毫不能遺忘已經被deliver的消息,若一條消息在一臺機器上被deliver,那麼該消息必須將在每臺機器上deliver

 咱們必須丟棄已經被skip的消息。

(3) 保證示例

第一條:

若一條消息在一臺機器上被deliver,那麼該消息必須將在每臺機器上deliver,即便那臺機器故障了。例如,出現了這樣一種狀況:Leader發送了commit消息,但在該commit消息到達其餘任何機器以前,Leader發生了故障。也就是說,只有Leader本身收到了commit消息。如圖3.4中的C2

 

圖3.4是"第一條保證"(deliver消息不能忘記)的一個示例。在該圖中Server1是一個Leader,咱們用L1表示,Server2和Server3爲Follower。首先Leader發起了兩個Proposal,P1和P2,並將P一、P2發送給了Server1和Server2。而後Leader對P1發起了Commit即C1,以後又發起了一個Proposal即P3,再後來又對P2發起了commit即C2,就在此時咱們的Leader掛了。那麼這時候,P3和C2這兩個消息只有Leader本身收到了。

由於Leader已經deliver了該C2消息,client可以在消息中看到該事務的結果。因此該事務必須可以在其餘全部的Server中deliver,最終使得client看到了一個一致性的服務視圖。

第二條:

一個被skip的消息,必須仍然須要被skip。例如,發生了這樣一種狀況:Leader發送了propose消息,但在該propose消息到達其餘任何機器以前,Leader發生了故障。也就是說,只有Leader本身收到了propose消息。如圖3.4中的P3所示。

在圖3.4中沒有任何一個server可以看到3號提議,因此在圖3.5中當server 1恢復時他須要在系統恢復時丟棄三號提議P3。

 

在圖3.5是"第二條保證"(skip消息必須被丟棄)的一個示例。Server1掛掉之後,Server3被選舉爲Leader,咱們用L2表示。L2中還有未被deliver的消息P一、P2,因此,L2在發出新提議P1000000一、P10000002以前,L2先將P一、P2兩個消息deliver。所以,L2先發出了兩個commit消息C一、C2,以後L2才發出了新的提議P10000001和P10000002。

若是Server1 恢復以後再次成爲了Leader,此時再次將P3在P10000001和P10000002以後deliver,那麼將違背順序性的保障。

(4) 保證的實現

  若是Leader選舉協議保證了新LeaderQuorum Server中具備最高的提議編號,即Zxid最高。那麼新選舉出來的leader將具備全部已deliver的消息。新選舉出來的Leader,在提出一個新消息以前,首先要保證事務日誌中的全部消息都由Quorum Follower已Propose並deliver。須要注意的是,咱們可讓新Leader成爲一個用最高zxid來處理事務的server,來做爲一個優化。這樣,做爲新被選舉出來的Leader,就沒必要去從一組Followers中找出包含最高zxid的Followers和獲取丟失的事務。

① 第一條

全部的正確啓動的Servers,將會成爲Leader或者跟隨一個Leader。Leader可以確保它的Followers看到全部的提議,並deliver全部已經deliver的消息。經過將新鏈接上的Follower所沒有見過的全部PROPOSAL進行排隊,並以後對該Proposals的COMMIT消息進行排隊,直到最後一個COMMIT消息。在全部這樣的消息已經排好隊以後,Leader將會把Follower加入到廣播列表,以便從此的提議和確認。這一條是爲了保證一致性,由於若是一條消息P已經在舊Leader-Server1中deliver了,即便它剛剛將消息P deliver以後就掛了,可是當舊Leader-Server1重啓恢復以後,咱們的Client就能夠從該Server中看到該消息P deliver的事務,因此爲了保證每個client都能看到一個一致性的視圖,咱們須要將該消息在每一個Server上deliver。

② 第二條

skip已經Propose,但不能deliver的消息,處理起來也比較簡單。在咱們的實現中,Zxid是由64位數字組成的,低32位用做簡單計數器高32位是一個epoch。每當新Leader接管它時,將獲取日誌中Zxid最大的epoch,新Leader Zxidepoch位設置爲epoch+1,counter位設置0。用epoch來標記領導關係的改變,並要求Quorum Servers 經過epoch來識別該leader,避免了多個Leader用同一個Zxid發佈不一樣的提議。

這 個方案的一個優勢就是,咱們能夠skip一個失敗的領導者的實例,從而加速並簡化了恢復過程。若是一臺宕機的Server重啓,並帶有未發佈的 Proposal,那麼先前的未發佈的全部提議將永不會被deliver。而且它不可以成爲一個新leader,由於任何一種可能的 Quorum Servers ,都會有一個Server其Proposal 來自與一個新epoch所以它具備一個較高的zxid。當Server以Follower的身份鏈接,領導者檢查自身最後提交的提議,該提議的epoch 爲Follower的最新提議的epoch(也就是圖3.5中新Leader-Server2中deliver的C2提議),並告訴Follower截斷 事務日誌直到該epoch在新Leader中deliver的最後的Proposal即C2。在圖3.5中,當舊Leader-Server1鏈接到了新leader-Server2,leader將告訴他從事務日誌中清除3號提議P3,具體點就是清除P2以後的全部提議,由於P2以後的全部提議只有舊Leader-Server1知道,其餘Server不知道。

(5) Paxos與Zab

① Paxos一致性

Paxos的一致性不能達到ZooKeeper的要求,咱們能夠下面一個例子。咱們假設ZK集羣由三臺機器組成,Server一、Server二、Server3。Server1爲Leader,他生成了 三條Proposal,P一、P二、P3。可是在發送完P1以後,Server1就掛了。以下圖3.6所示。

 

Server1掛掉以後,Server3被選舉成爲Leader,由於在Server3裏只有一條Proposal—P1。因此,Server3在P1的基礎之上又發出了一條新Proposal—P2',P2'的Zxid爲02。以下圖3.7所示。

Server2發送完P2'以後,它也掛了。此時Server1已經重啓恢復,並再次成爲了Leader。那麼,Server1將發送尚未被deliver的Proposal—P2和P3。因爲Follower-Server2中P2'的Zxid爲02和Leader-Server1中P2的Zxid相等,因此P2會被拒絕。而P3,將會被Server2接受。如圖3.8所示。

 

咱們分析一下Follower-Server2中的Proposal,因爲P2'將P2的內容覆蓋了。因此致使,Server2中的Proposal-P3沒法生效,由於他的父節點並不存在。

② Zab一致性

首先來分析一下,上面的示例中爲何不知足ZooKeeper需求。ZooKeeper是一個樹形結構,不少操做都要先檢查才能肯定能不能執行,好比,在圖3.8中Server2有三條Proposal。P1的事務是建立節點"/zk",P2'是建立節點"/c",而P3是建立節點"/a/b",因爲"/a"還沒建,建立"a/b"就搞不定了。那麼,咱們就能今後看出Paxos的一致性達不到ZooKeeper一致性的要求。

爲了達到ZooKeeper所須要的一致性,ZooKeeper採用了Zab協議。Zab作了以下幾條保證,來達到ZooKeeper要求的一致性。

(a) Zab要保證同一個leader的發起的事務要按順序被apply,同時還要保證只有先前的leader的全部事務都被apply以後,新選的leader才能在發起事務。

(b) 一些已經Skip的消息,須要仍然被Skip。

我想對於第一條保證你們都能理解,它主要是爲了保證每 個Server的數據視圖的一致性。我重點解釋一下第二條,它是如何實現。爲了可以實現,Skip已經被skip的消息。咱們在Zxid中引入了 epoch,以下圖所示。每當Leader發生變換時,epoch位就加1,counter位置0。

 

咱們繼續使用上面的例子,看一下他是如何實現Zab的 第二條保證的。咱們假設ZK集羣由三臺機器組成,Server一、Server二、Server3。Server1爲Leader,他生成了三條 Proposal,P一、P二、P3。可是在發送完P1以後,Server1就掛了。以下圖3.10所示。

 

Server1掛掉以後,Server3被選舉成爲 Leader,由於在Server3裏只有一條Proposal—P1。因此,Server3在P1的基礎之上又發出了一條新Proposal—P2', 因爲Leader發生了變換,epoch要加1,因此epoch由原來的0變成了1,而counter要置0。那麼,P2'的Zxid爲10。以下圖3.11所示。

 

 

 Server2發送完P2'以後,它也掛了。此時Server1已經重啓恢復,並再次成爲了Leader。那麼,Server1將發送尚未被deliver的Proposal—P2和P3。因爲Server2中P2'的Zxid爲10,而Leader-Server1中P2P3的Zxid分別爲0203P2'的epoch位高於P2和P3。因此此時Leader-Server1的P2和P3都會被拒絕,那麼咱們Zab的第二條保證也就實現了。如圖3.12所示。

相關文章
相關標籤/搜索