淺談分佈式服務協調技術 Zookeeper

Google的三篇論文影響了很多很多人,也影響了很多很多系統。這三篇論文一直是分佈式領域傳閱的經典。根據MapReduce,於是我們有了Hadoop;根據GFS,於是我們有了HDFS;根據BigTable,於是我們有了HBase。而在這三篇論文裏都提及Google的一個Lock Service —— Chubby,哦,於是我們有了Zookeeper。

隨着大數據的火熱,Hxx們已經變得耳熟能詳,現在作爲一個開發人員如果都不知道這幾個名詞出門都好像不好意思跟人打招呼。但實際上對我們這些非大數據開發人員而言,Zookeeper是比Hxx們可能接觸到更多的一個基礎服務。但是,無奈的是它一直默默的位於二線,從來沒有Hxx們那麼耀眼。那麼到底什麼是Zookeeper呢?Zookeeper可以用來幹什麼?我們將如何使用Zookeeper?Zookeeper又是怎麼實現的?


什麼是Zookeeper

在Zookeeper的官網上有這麼一句話:ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services。

這大概描述了Zookeeper主要是一個分佈式服務協調框架,實現同步服務,配置維護和命名服務等分佈式應用。是一個高性能的分佈式數據一致性解決方案。

通俗地講,ZooKeeper是動物園管理員,它是拿來管大象 Hadoop、鯨魚 HBase、Kafka等的管理員。


Zookeeper和CAP的關係

作爲一個分佈式系統,分區容錯性是一個必須要考慮的關鍵點。一個分佈式系統一旦喪失了分區容錯性,也就表示放棄了擴展性。因爲在分佈式系統中,網絡故障是經常出現的,一旦出現在這種問題就會導致整個系統不可用是絕對不能容忍的。所以,大部分分佈式系統都會在保證分區容錯性的前提下在一致性和可用性之間做權衡。

ZooKeeper是個CP(一致性+分區容錯性)的,即任何時刻對ZooKeeper的訪問請求能得到一致的數據結果,同時系統對網絡分割具備容錯性;但是它不能保證每次服務請求的可用性。也就是在極端環境下,ZooKeeper可能會丟棄一些請求,消費者程序需要重新請求才能獲得結果。

ZooKeeper是分佈式協調服務,它的職責是保證數據在其管轄下的所有服務之間保持同步、一致;所以就不難理解爲什麼ZooKeeper被設計成CP而不是AP特性的了。而且, 作爲ZooKeeper的核心實現算法Zab,就是解決了分佈式系統下數據如何在多個服務之間保持同步問題的。


Zookeeper節點特性及節點屬性分析

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

數據模型

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

Zookeeper中ZNode的節點創建時候是可以指定類型的,主要有下面幾種類型。

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

  • EPHEMERAL:臨時ZNode節點,Client連接到Zookeeper Service的時候會建立一個Session,之後用這個Zookeeper連接實例創建該類型的znode,一旦Client關閉了Zookeeper的連接,服務器就會清除Session,然後這個Session建立的ZNode節點都會從命名空間消失。總結就是,這個類型的znode的生命週期是和Client建立的連接一樣的。

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

  • EPEMERAL_SEQUENTIAL:臨時自動編號節點,ZNode節點編號會自動增加,但是會隨Session消失而消失

Watcher數據變更通知

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

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

ACL保障數據的安全

Zookeeper內部存儲了分佈式系統運行時狀態的元數據,這些元數據會直接影響基於Zookeeper進行構造的分佈式系統的運行狀態,如何保障系統中數據的安全,從而避免因誤操作而帶來的數據隨意變更而導致的數據庫異常十分重要,Zookeeper提供了一套完善的ACL權限控制機制來保障數據的安全。

我們可以從三個方面來理解ACL機制:權限模式 Scheme、授權對象 ID、權限 Permission,通常使用"scheme:id:permission"來標識一個有效的ACL信息。

內存數據

Zookeeper的數據模型是樹結構,在內存數據庫中,存儲了整棵樹的內容,包括所有的節點路徑、節點數據、ACL信息,Zookeeper會定時將這個數據存儲到磁盤上。

  • DataTree:DataTree是內存數據存儲的核心,是一個樹結構,代表了內存中一份完整的數據。DataTree不包含任何與網絡、客戶端連接及請求處理相關的業務邏輯,是一個獨立的組件。

  • DataNode:DataNode是數據存儲的最小單元,其內部除了保存了結點的數據內容、ACL列表、節點狀態之外,還記錄了父節點的引用和子節點列表兩個屬性,其也提供了對子節點列表進行操作的接口。

  • ZKDatabase:Zookeeper的內存數據庫,管理Zookeeper的所有會話、DataTree存儲和事務日誌。ZKDatabase會定時向磁盤dump快照數據,同時在Zookeeper啓動時,會通過磁盤的事務日誌和快照文件恢復成一個完整的內存數據庫。


Zookeeper的實現原理分析

1. Zookeeper Service網絡結構

Zookeeper的工作集羣可以簡單分成兩類,一個是Leader,唯一一個,其餘的都是follower,如何確定Leader是通過內部選舉確定的。

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

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

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

2. Zookeeper讀寫數據

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

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

3. 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所在的服務器,並與其進行數據同步,然後一起參與到消息廣播流程中去。

Zookeeper只允許唯一的一個Leader服務器來進行事務請求的處理,Leader服務器在接收到客戶端的事務請求後,會生成對應的事務提議併發起一輪廣播協議,而如果集羣中的其他機器收到客戶端的事務請求後,那麼這些非Leader服務器會首先將這個事務請求轉發給Leader服務器。

消息廣播

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

  • ZooKeeper使用單一主進程Leader用於處理客戶端所有事務請求,並採用Zab的原子廣播協議,將服務器數據狀態變更以事務Proposal的形式廣播Follower上,因此能很好的處理客戶端的大量併發請求。

  • 另一方面,由於事務間可能存在着依賴關係,Zab協議保證Leader廣播的變更序列被順序的處理,有些狀態的變更必須依賴於比它早生成的那些狀態變更。

  • 最後,考慮到主進程Leader在任何時候可能崩潰或者異常退出, 因此Zab協議還要Leader進程崩潰的時候可以重新選出Leader並且保證數據的完整性;Follower收到Proposal後,寫到磁盤,返回Ack。Leader收到大多數ACK後,廣播Commit消息,自己也提交該消息。Follower收到Commit之後,提交該消息。

Zab協議簡化了2PC事務提交:

  • 去除中斷邏輯移除,follower要麼ack,要麼拋棄Leader。

  • Leader不需要所有的Follower都響應成功,只要一個多數派Ack即可。

崩潰恢復

上面我們講了Zab協議在正常情況下的消息廣播過程,那麼一旦Leader服務器出現崩潰或者與過半的follower服務器失去聯繫,就進入崩潰恢復模式。

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


Zookeeper實踐,共享鎖,Leader選舉

分佈式鎖用於控制分佈式系統之間同步訪問共享資源的一種方式,可以保證不同系統訪問一個或一組資源時的一致性,主要分爲排它鎖和共享鎖。

  • 排它鎖又稱爲寫鎖或獨佔鎖,若事務T1對數據對象O1加上了排它鎖,那麼在整個加鎖期間,只允許事務T1對O1進行讀取和更新操作,其他任何事務都不能再對這個數據對象進行任何類型的操作,直到T1釋放了排它鎖。

  • 共享鎖又稱爲讀鎖,若事務T1對數據對象O1加上共享鎖,那麼當前事務只能對O1進行讀取操作,其他事務也只能對這個數據對象加共享鎖,直到該數據對象上的所有共享鎖都被釋放。


推薦文章:基於Zk實現分佈式鎖

Leader選舉

Leader選舉是保證分佈式數據一致性的關鍵所在。當Zookeeper集羣中的一臺服務器出現以下兩種情況之一時,需要進入Leader選舉。

  • 服務器初始化啓動。

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

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越大機會越大。


本文轉載自 linkedkeeper.com 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章