天天看點

zookeeper原理1 Zookeeper的基本概念2 ZooKeeper的工作原理

ZooKeeper是一個分布式的,開放源碼的分布式應用程式協調服務,它包含一個簡單的原語集,分布式應用程式可以基于它實作同步服務,配置維護和命名服務等。Zookeeper是hadoop的一個子項目,其發展曆程無需贅述。在分布式應用中,由于工程師不能很好地使用鎖機制,以及基于消息的協調機制不适合在某些應用中使用,是以需要有一種可靠的、可擴充的、分布式的、可配置的協調機制來統一系統的狀态。Zookeeper的目的就在于此。本文簡單分析zookeeper的工作原理,對于如何使用zookeeper不是本文讨論的重點。

Zookeeper中的角色主要有以下三類,如下表所示:

zookeeper原理1 Zookeeper的基本概念2 ZooKeeper的工作原理

系統模型如圖所示:

zookeeper原理1 Zookeeper的基本概念2 ZooKeeper的工作原理

1.最終一緻性:client不論連接配接到哪個Server,展示給它都是同一個視圖,這是zookeeper最重要的性能。

2 .可靠性:具有簡單、健壯、良好的性能,如果消息m被到一台伺服器接受,那麼它将被所有的伺服器接受。

3 .實時性:Zookeeper保證用戶端将在一個時間間隔範圍内獲得伺服器的更新資訊,或者伺服器失效的資訊。但由于網絡延時等原因,Zookeeper不能保證兩個用戶端能同時得到剛更新的資料,如果需要最新資料,應該在讀資料之前調用sync()接口。

4 .等待無關(wait-free):慢的或者失效的client不得幹預快速的client的請求,使得每個client都能有效的等待。

5.原子性:更新隻能成功或者失敗,沒有中間狀态。

6 .順序性:包括全局有序和偏序兩種:全局有序是指如果在一台伺服器上消息a在消息b前釋出,則在所有Server上消息a都将在消息b前被釋出;偏序是指如果一個消息b在消息a後被同一個發送者釋出,a必将排在b前面。

Zookeeper的核心是原子廣播,這個機制保證了各個Server之間的同步。實作這個機制的協定叫做Zab協定。Zab協定有兩種模式,它們分别是恢複模式(選主)和廣播模式(同步)。當服務啟動或者在上司者崩潰後,Zab就進入了恢複模式,當上司者被選舉出來,且大多數Server完成了和leader的狀态同步以後,恢複模式就結束了。狀态同步保證了leader和Server具有相同的系統狀态。

為了保證事務的順序一緻性,zookeeper采用了遞增的事務id号(zxid)來辨別事務。所有的提議(proposal)都在被提出的時候加上了zxid。實作中zxid是一個64位的數字,它高32位是epoch用來辨別leader關系是否改變,每次一個leader被選出來,它都會有一個新的epoch,辨別目前屬于那個leader的統治時期。低32位用于遞增計數。

每個Server在工作過程中有三種狀态:

LOOKING:目前Server不知道leader是誰,正在搜尋

LEADING:目前Server即為選舉出來的leader

FOLLOWING:leader已經選舉出來,目前Server與之同步

當leader崩潰或者leader失去大多數的follower,這時候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會記錄事務日志并定期進行快照,友善在恢複時進行狀态恢複。選主的具體流程圖如下所示:

zookeeper原理1 Zookeeper的基本概念2 ZooKeeper的工作原理

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

zookeeper原理1 Zookeeper的基本概念2 ZooKeeper的工作原理

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

1. leader等待server連接配接;

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

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

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

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

流程圖如下所示:

zookeeper原理1 Zookeeper的基本概念2 ZooKeeper的工作原理

Leader主要有三個功能:

1 .恢複資料;

2 .維持與Learner的心跳,接收Learner請求并判斷Learner的請求消息類型;

3 .Learner的消息類型主要有PING消息、REQUEST消息、ACK消息、REVALIDATE消息,根據不同的消息類型,進行不同的處理。

PING消息是指Learner的心跳資訊;REQUEST消息是Follower發送的提議資訊,包括寫請求及同步請求;ACK消息是Follower的對提議的回複,超過半數的Follower通過,則commit該提議;REVALIDATE消息是用來延長SESSION有效時間。

zookeeper原理1 Zookeeper的基本概念2 ZooKeeper的工作原理

Follower主要有四個功能:

1. 向Leader發送請求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);

2 .接收Leader消息并進行處理;

3 .接收Client的請求,如果為寫請求,發送給Leader進行投票;

4 .傳回Client結果。

Follower的消息循環處理如下幾種來自Leader的消息:

1 .PING消息: 心跳消息;

2 .PROPOSAL消息:Leader發起的提案,要求Follower投票;

3 .COMMIT消息:伺服器端最新一次提案的資訊;

4 .UPTODATE消息:表明同步完成;

5 .REVALIDATE消息:根據Leader的REVALIDATE結果,關閉待revalidate的session還是允許其接受消息;

6 .SYNC消息:傳回SYNC結果到用戶端,這個消息最初由用戶端發起,用來強制得到最新的更新。

Follower的工作流程簡圖如下所示,在實際實作中,Follower是通過5個線程來實作功能的。

zookeeper原理1 Zookeeper的基本概念2 ZooKeeper的工作原理

對于observer的流程不再叙述,observer流程和Follower的唯一不同的地方就是observer不會參加leader發起的投票。

主流應用場景:

Zookeeper的主流應用場景實作思路(除去官方示例) 

(1)配置管理

集中式的配置管理在應用叢集中是非常常見的,一般商業公司内部都會實作一套集中的配置管理中心,應對不同的應用叢集對于共享各自配置的需求,并且在配置變更時能夠通知到叢集中的每一個機器。

Zookeeper很容易實作這種集中式的配置管理,比如将APP1的所有配置配置到/APP1 znode下,APP1所有機器一啟動就對/APP1這個節點進行監控(zk.exist("/APP1",true)),并且實作回調方法Watcher,那麼在zookeeper上/APP1 znode節點下資料發生變化的時候,每個機器都會收到通知,Watcher方法将會被執行,那麼應用再取下資料即可(zk.getData("/APP1",false,null));

zookeeper原理1 Zookeeper的基本概念2 ZooKeeper的工作原理

(2)叢集管理 

應用叢集中,我們常常需要讓每一個機器知道叢集中(或依賴的其他某一個叢集)哪些機器是活着的,并且在叢集機器因為當機,網絡斷鍊等原因能夠不在人工介入的情況下迅速通知到每一個機器。

Zookeeper同樣很容易實作這個功能,比如我在zookeeper伺服器端有一個znode叫/APP1SERVERS,那麼叢集中每一個機器啟動的時候都去這個節點下建立一個EPHEMERAL類型的節點,比如server1建立/APP1SERVERS/SERVER1(可以使用ip,保證不重複),server2建立/APP1SERVERS/SERVER2,然後SERVER1和SERVER2都watch /APP1SERVERS這個父節點,那麼也就是這個父節點下資料或者子節點變化都會通知對該節點進行watch的用戶端。因為EPHEMERAL類型節點有一個很重要的特性,就是用戶端和伺服器端連接配接斷掉或者session過期就會使節點消失,那麼在某一個機器挂掉或者斷鍊的時候,其對應的節點就會消失,然後叢集中所有對/APP1SERVERS進行watch的用戶端都會收到通知,然後取得最新清單即可。

另外有一個應用場景就是叢集選master,一旦master挂掉能夠馬上能從slave中選出一個master,實作步驟和前者一樣,隻是機器在啟動的時候在APP1SERVERS建立的節點類型變為EPHEMERAL_SEQUENTIAL類型,這樣每個節點會自動被編号

我們預設規定編号最小的為master,是以當我們對/APP1SERVERS節點做監控的時候,得到伺服器清單,隻要所有叢集機器邏輯認為最小編号節點為master,那麼master就被選出,而這個master當機的時候,相應的znode會消失,然後新的伺服器清單就被推送到用戶端,然後每個節點邏輯認為最小編号節點為master,這樣就做到動态master選舉。

zookeeper原理1 Zookeeper的基本概念2 ZooKeeper的工作原理

Zookeeper C API 的聲明和描述在 include/zookeeper.h 中可以找到,另外大部分的 Zookeeper C API 常量、結構體聲明也在 zookeeper.h 中,如果如果你在使用 C API 是遇到不明白的地方,最好看看 zookeeper.h,或者自己使用 doxygen 生成 Zookeeper C API 的幫助文檔。

Zookeeper 中最有特色且最不容易了解的是監視(Watches)。Zookeeper 所有的讀操作——getData(), getChildren(), 和 exists() 都 可以設定監視(watch),監視事件可以了解為一次性的觸發器, 官方定義如下: 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。對此需要作出如下了解:

(一次性觸發)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). 網絡延遲或者其他因素可能導緻不同的用戶端在不同的時刻感覺某一監視事件,但是不同的用戶端所看到的一切具有一緻的順序。

(被設定 watch 的資料)The data for which the watch was set

這意味着 znode 節點本身具有不同的改變方式。你也可以想象 Zookeeper 維護了兩條監視連結清單:資料監視和子節點監視(data watches and child watches) getData() and exists() 設定資料監視,getChildren() 設定子節點監視。 或者,你也可以想象 Zookeeper 設定的不同監視傳回不同的資料,getData() 和 exists() 傳回 znode 節點的相關資訊,而 getChildren() 傳回子節點清單。是以, setData() 會觸發設定在某一節點上所設定的資料監視(假定資料設定成功),而一次成功的 create() 操作則會出發目前節點上所設定的資料監視以及父節點的子節點監視。一次成功的 delete() 操作将會觸發目前節點的資料監視和子節點監視事件,同時也會觸發該節點父節點的child watch。

Zookeeper 中的監視是輕量級的,是以容易設定、維護和分發。當用戶端與 Zookeeper 伺服器端失去聯系時,用戶端并不會收到監視事件的通知,隻有當用戶端重新連接配接後,若在必要的情況下,以前注冊的監視會重新被注冊并觸發,對于開發人員來說 這通常是透明的。隻有一種情況會導緻監視事件的丢失,即:通過 exists() 設定了某個 znode 節點的監視,但是如果某個用戶端在此 znode 節點被建立和删除的時間間隔内與 zookeeper 伺服器失去了聯系,該用戶端即使稍後重新連接配接 zookeeper伺服器後也得不到事件通知。

struct Id 結構為:

struct ACL 結構為:

struct ACL_vector 結構為:

與 znode 通路權限有關的常量

const int ZOO_PERM_READ; //允許用戶端讀取 znode 節點的值以及子節點清單。

const int ZOO_PERM_WRITE;// 允許用戶端設定 znode 節點的值。

const int ZOO_PERM_CREATE; //允許用戶端在該 znode 節點下建立子節點。

const int ZOO_PERM_DELETE;//允許用戶端删除子節點。

const int ZOO_PERM_ADMIN; //允許用戶端執行 set_acl()。

const int ZOO_PERM_ALL;//允許用戶端執行所有操作,等價與上述所有标志的或(OR) 。

與 ACL IDs 相關的常量

struct Id ZOO_ANYONE_ID_UNSAFE; //(‘world’,’anyone’)

struct Id ZOO_AUTH_IDS;// (‘auth’,’’)

三種标準的 ACL

struct ACL_vector ZOO_OPEN_ACL_UNSAFE; //(ZOO_PERM_ALL,ZOO_ANYONE_ID_UNSAFE)

struct ACL_vector ZOO_READ_ACL_UNSAFE;// (ZOO_PERM_READ, ZOO_ANYONE_ID_UNSAFE)

struct ACL_vector ZOO_CREATOR_ALL_ACL; //(ZOO_PERM_ALL,ZOO_AUTH_IDS)

這 兩個常量用于辨別感興趣的事件并通知 zookeeper 發生了哪些事件。Interest 常量可以進行組合或(OR)來辨別多種興趣(multiple interests: write, read),這兩個常量一般用于 zookeeper_interest() 和 zookeeper_process()兩個函數中。

zoo_create 函數标志,ZOO_EPHEMERAL 用來辨別建立臨時節點,ZOO_SEQUENCE 用來辨別節點命名具有遞增的字尾序号(一般是節點名稱後填充 10 位字元的序号,如 /xyz0000000000, /xyz0000000001, /xyz0000000002, ...),同樣地,ZOO_EPHEMERAL, ZOO_SEQUENCE 可以組合。

以下常量均與 Zookeeper 連接配接狀态有關,他們通常用作螢幕回調函數的參數。

ZOOAPI const int 

ZOO_EXPIRED_SESSION_STATE

ZOO_AUTH_FAILED_STATE

ZOO_CONNECTING_STATE

ZOO_ASSOCIATING_STATE

ZOO_CONNECTED_STATE

以下常量辨別監視事件的類型,他們通常用作螢幕回調函數的第一個參數。

<a>ZOO_CREATED_EVENT; // 節點被建立(此前該節點不存在),通過 zoo_exists() 設定監視。</a>

<a>ZOO_CHANGED_EVENT; // 節點發生變化,通過 zoo_exists() 和 zoo_get() 設定監視。</a>

<a>ZOO_CHILD_EVENT; // 子節點事件,通過zoo_get_children() 和 zoo_get_children2()設定監視。</a>

<a>ZOO_SESSION_EVENT; // 會話丢失</a>

<a>ZOO_NOTWATCHING_EVENT; // 監視被移除。</a>

ZOK 

正常傳回

ZSYSTEMERROR 

系統或伺服器端錯誤(System and server-side errors),伺服器不會抛出該錯誤,該錯誤也隻是用來辨別錯誤範圍的,即大于該錯誤值,且小于 ZAPIERROR 都是系統錯誤。

ZRUNTIMEINCONSISTENCY 

運作時非一緻性錯誤。

ZDATAINCONSISTENCY 

資料非一緻性錯誤。

ZCONNECTIONLOSS 

Zookeeper 用戶端與伺服器端失去連接配接

ZMARSHALLINGERROR 

在 marshalling 和 unmarshalling 資料時出現錯誤(Error while marshalling or unmarshalling data)

ZUNIMPLEMENTED 

該操作未實作(Operation is unimplemented)

ZOPERATIONTIMEOUT 

該操作逾時(Operation timeout)

ZBADARGUMENTS 

非法參數錯誤(Invalid arguments)

ZINVALIDSTATE 

非法句柄狀态(Invliad zhandle state)

ZAPIERROR 

API 錯誤(API errors),伺服器不會抛出該錯誤,該錯誤也隻是用來辨別錯誤範圍的,錯誤值大于該值的辨別 API 錯誤,而小于該值的辨別 ZSYSTEMERROR。

ZNONODE 

節點不存在(Node does not exist)

ZNOAUTH 

沒有經過授權(Not authenticated)

ZBADVERSION 

版本沖突(Version conflict)

ZNOCHILDRENFOREPHEMERALS 

臨時節點不能擁有子節點(Ephemeral nodes may not have children)

ZNODEEXISTS 

節點已經存在(The node already exists)

ZNOTEMPTY 

該節點具有自身的子節點(The node has children)

ZSESSIONEXPIRED 

會話過期(The session has been expired by the server)

ZINVALIDCALLBACK 

非法的回調函數(Invalid callback specified)

ZINVALIDACL 

非法的ACL(Invalid ACL specified)

ZAUTHFAILED 

用戶端授權失敗(Client authentication failed)

ZCLOSING 

Zookeeper 連接配接關閉(ZooKeeper is closing)

ZNOTHING 

并非錯誤,用戶端不需要處理伺服器的響應(not error, no server responses to process)

ZSESSIONMOVED 

會話轉移至其他伺服器,是以操作被忽略(session moved to another server, so operation is ignored)

ZOO_CREATED_EVENT:節點建立事件,需要watch一個不存在的節點,當節點被建立時觸發,此watch通過zoo_exists()設定

ZOO_DELETED_EVENT:節點删除事件,此watch通過zoo_exists()或zoo_get()設定

ZOO_CHANGED_EVENT:節點資料改變事件,此watch通過zoo_exists()或zoo_get()設定

ZOO_CHILD_EVENT:子節點清單改變事件,此watch通過zoo_get_children()或zoo_get_children2()設定

ZOO_SESSION_EVENT:會話失效事件,用戶端與服務端斷開或重連時觸發

ZOO_NOTWATCHING_EVENT:watch移除事件,服務端出于某些原因不再為用戶端watch節點時觸發

繼續閱讀