Zookeeper架構

zookeeper是什麼

ZooKeeper是一個分佈式的,開放源碼的分佈式應用程序協調服務,是Google的Chubby一個開源的實現,是Hadoop的重要組件,CDH版本中更是使用它進行Namenode的協調控制。它是一個爲分佈式應用提供一致性服務的軟件,提供的功能包括:配置維護、名字服務、分佈式同步、組服務等。ZooKeeper的目標就是封裝好複雜易出錯的關鍵服務,將簡單易用的接口和性能高效、功能穩定的系統提供給用戶。node

Zookeeper的基本概念

(1) 角色
Zookeeper中的角色主要有如下三類,以下表所示:網絡

系統模型如圖所示:數據結構

(2)重要概念
ZNode
前文已介紹了ZNode, ZNode根據其自己的特性,能夠分爲下面兩類:
    Regular ZNode: 常規型ZNode, 用戶須要顯式的建立、刪除
    Ephemeral ZNode: 臨時型ZNode, 用戶建立它以後,能夠顯式的刪除,也能夠在建立它的Session結束後,由ZooKeeper Server自動刪除
ZNode還有一個Sequential的特性,若是建立的時候指定的話,該ZNode的名字後面會自動Append一個不斷增長的SequenceNo。
Session
Client與ZooKeeper之間的通訊,須要建立一個Session,這個Session會有一個超時時間。由於ZooKeeper集羣會把Client的Session信息持久化,因此在Session沒超時以前,Client與ZooKeeper Server的鏈接能夠在各個ZooKeeper Server之間透明地移動。
在實際的應用中,若是Client與Server之間的通訊足夠頻繁,Session的維護就不須要其它額外的消息了。不然,ZooKeeper Client會每t/3 ms發一次心跳給Server,若是Client 2t/3 ms沒收到來自Server的心跳回應,就會換到一個新的ZooKeeper Server上。這裏t是用戶配置的Session的超時時間。
Watcher
ZooKeeper支持一種Watch操做,Client能夠在某個ZNode上設置一個Watcher,來Watch該ZNode上的變化。若是該ZNode上有相應的變化,就會觸發這個Watcher,把相應的事件通知給設置Watcher的Client。須要注意的是,ZooKeeper中的Watcher是一次性的,即觸發一次就會被取消,若是想繼續Watch的話,須要客戶端從新設置Watcher。這個跟epoll裏的oneshot模式有點相似。併發

(3)特性分佈式

順序性,client的updates請求都會根據它發出的順序被順序的處理;
原子性,  一個update操做要麼成功要麼失敗,沒有其餘可能的結果;
一致的鏡像,client不論鏈接到哪一個server,展現給它都是同一個視圖;
可靠性,一旦一個update被應用就被持久化了,除非另外一個update請求更新了當前值
實時性,對於每一個client它的系統視圖都是最新的oop

ZooKeeper Client API

ZooKeeper Client Library提供了豐富直觀的API供用戶程序使用,下面是一些經常使用的API:
    create(path, data, flags): 建立一個ZNode, path是其路徑,data是要存儲在該ZNode上的數據,flags經常使用的有: PERSISTEN, PERSISTENT_SEQUENTAIL, EPHEMERAL, EPHEMERAL_SEQUENTAIL
    delete(path, version): 刪除一個ZNode,能夠經過version刪除指定的版本, 若是version是-1的話,表示刪除全部的版本
    exists(path, watch): 判斷指定ZNode是否存在,並設置是否Watch這個ZNode。這裏若是要設置Watcher的話,Watcher是在建立ZooKeeper實例時指定的,若是要設置特定的Watcher的話,能夠調用另外一個重載版本的exists(path, watcher)。如下幾個帶watch參數的API也都相似
    getData(path, watch): 讀取指定ZNode上的數據,並設置是否watch這個ZNode
    setData(path, watch): 更新指定ZNode的數據,並設置是否Watch這個ZNode
    getChildren(path, watch): 獲取指定ZNode的全部子ZNode的名字,並設置是否Watch這個ZNode
    sync(path): 把全部在sync以前的更新操做都進行同步,達到每一個請求都在半數以上的ZooKeeper Server上生效。path參數目前沒有用
    setAcl(path, acl): 設置指定ZNode的Acl信息
    getAcl(path): 獲取指定ZNode的Acl信息性能

 特色

讀、寫(更新)模式spa

在ZooKeeper集羣中,讀能夠從任意一個ZooKeeper Server讀,這一點是保證ZooKeeper比較好的讀性能的關鍵;寫的請求會先Forwarder到Leader,而後由Leader來經過ZooKeeper中的原子廣播協議,將請求廣播給全部的Follower,Leader收到一半以上的寫成功的Ack後,就認爲該寫成功了,就會將該寫進行持久化,並告訴客戶端寫成功了。操作系統

WAL和Snapshot線程

和大多數分佈式系統同樣,ZooKeeper也有WAL(Write-Ahead-Log),對於每個更新操做,ZooKeeper都會先寫WAL, 而後再對內存中的數據作更新,而後向Client通知更新結果。另外,ZooKeeper還會按期將內存中的目錄樹進行Snapshot,落地到磁盤上,這個跟HDFS中的FSImage是比較相似的。這麼作的主要目的,一固然是數據的持久化,二是加快重啓以後的恢復速度,若是所有經過Replay WAL的形式恢復的話,會比較慢。

FIFO
對於每個ZooKeeper客戶端而言,全部的操做都是遵循FIFO順序的,這一特性是由下面兩個基本特性來保證的:一是ZooKeeper Client與Server之間的網絡通訊是基於TCP,TCP保證了Client/Server之間傳輸包的順序;二是ZooKeeper Server執行客戶端請求也是嚴格按照FIFO順序的。

Linearizability
在ZooKeeper中,全部的更新操做都有嚴格的偏序關係,更新操做都是串行執行的,這一點是保證ZooKeeper功能正確性的關鍵。

zookeeper的存儲結構

zookeeper中的數據是按照「樹」結構進行存儲的。並且znode節點還分爲4中不一樣的類型。

(1)、znode
根據本小結第一部分的描述,很顯然zookeeper集羣自身維護了一套數據結構。這個存儲結構是一個樹形結構,其上的每個節點,咱們稱之爲「znode」。

  • 每個znode默認可以存儲1MB的數據(對於記錄狀態性質的數據來講,夠了)

  • 可使用zkCli命令,登陸到zookeeper上,並經過ls、create、delete、sync等命令操做這些znode節點

  • znode除了名稱、數據之外,還有一套屬性:zxid。這套zid與時間戳對應,記錄zid不一樣的狀態(後續咱們將用到)

那麼每一個znode結構又是什麼樣的呢?以下圖所示:

此外,znode還有操做權限。若是咱們把以上幾類屬性細化,又能夠獲得如下屬性的細節:

  • czxid:建立節點的事務的zxid
  • mzxid:對znode最近修改的zxid
  • ctime:以距離時間原點(epoch)的毫秒數表示的znode建立時間
  • mtime:以距離時間原點(epoch)的毫秒數表示的znode最近修改時間
  • version:znode數據的修改次數
  • cversion:znode子節點修改次數
  • aversion:znode的ACL修改次數
  • ephemeralOwner:若是znode是臨時節點,則指示節點全部者的會話ID;若是不是臨時節點,則爲零。
  • dataLength:znode數據長度。
  • numChildren:znode子節點個數。

(2)、znode中的存在類型
咱們知道了zookeeper內部維護了一套數據結構:由znode構成的集合,znode的集合又是一個樹形結構。每個znode又有不少屬性進行描述。而且znode的存在性還分爲四類,以下如所示:

znode是由客戶端建立的,它和建立它的客戶端的內在聯繫,決定了它的存在性:

  • PERSISTENT-持久化節點:建立這個節點的客戶端在與zookeeper服務的鏈接斷開後,這個節點也不會被刪除(除非您使用API強制刪除)。

  • PERSISTENT_SEQUENTIAL-持久化順序編號節點:當客戶端請求建立這個節點A後,zookeeper會根據parent-znode的zxid狀態,爲這個A節點編寫一個全目錄惟一的編號(這個編號只會一直增加)。當客戶端與zookeeper服務的鏈接斷開後,這個節點也不會被刪除。

  • EPHEMERAL-臨時目錄節點:建立這個節點的客戶端在與zookeeper服務的鏈接斷開後,這個節點(還有涉及到的子節點)就會被刪除。

  • EPHEMERAL_SEQUENTIAL-臨時順序編號目錄節點:當客戶端請求建立這個節點A後,zookeeper會根據parent-znode的zxid狀態,爲這個A節點編寫一個全目錄惟一的編號(這個編號只會一直增加)。當建立這個節點的客戶端與zookeeper服務的鏈接斷開後,這個節點被刪除。

  • 另外,不管是EPHEMERAL仍是EPHEMERAL_SEQUENTIAL節點類型,在zookeeper的client異常終止後,節點也會被刪除。

ZooKeeper典型應用場景


1. 名字服務(NameService) 
分佈式應用中,一般須要一套完備的命令機制,既能產生惟一的標識,又方便人識別和記憶。 咱們知道,每一個ZNode均可以由其路徑惟一標識,路徑自己也比較簡潔直觀,另外ZNode上還能夠存儲少許數據,這些都是實現統一的NameService的基礎。下面以在HDFS中實現NameService爲例,來講明實現NameService的基本布驟:

目標:經過簡單的名字來訪問指定的HDFS機羣
定義命名規則:這裏要作到簡潔易記憶。下面是一種可選的方案: [serviceScheme://][zkCluster]-[clusterName],好比hdfs://lgprc-example/表示基於lgprc ZooKeeper集羣的用來作example的HDFS集羣
配置DNS映射: 將zkCluster的標識lgprc經過DNS解析到對應的ZooKeeper集羣的地址
建立ZNode: 在對應的ZooKeeper上建立/NameService/hdfs/lgprc-example結點,將HDFS的配置文件存儲於該結點下
用戶程序要訪問hdfs://lgprc-example/的HDFS集羣,首先經過DNS找到lgprc的ZooKeeper機羣的地址,而後在ZooKeeper的/NameService/hdfs/lgprc-example結點中讀取到HDFS的配置,進而根據獲得的配置,獲得HDFS的實際訪問入口
2. 配置管理(Configuration Management) 
在分佈式系統中,常會遇到這樣的場景: 某個Job的不少個實例在運行,它們在運行時大多數配置項是相同的,若是想要統一改某個配置,一個個實例去改,是比較低效,也是比較容易出錯的方式。經過ZooKeeper能夠很好的解決這樣的問題,下面的基本的步驟:

將公共的配置內容放到ZooKeeper中某個ZNode上,好比/service/common-conf
全部的實例在啓動時都會傳入ZooKeeper集羣的入口地址,而且在運行過程當中Watch /service/common-conf這個ZNode
若是集羣管理員修改了了common-conf,全部的實例都會被通知到,根據收到的通知更新本身的配置,並繼續Watch /service/common-conf
3. 組員管理(Group Membership) 
在典型的Master-Slave結構的分佈式系統中,Master須要做爲「總管」來管理全部的Slave, 當有Slave加入,或者有Slave宕機,Master都須要感知到這個事情,而後做出對應的調整,以便不影響整個集羣對外提供服務。以HBase爲例,HMaster管理了全部的RegionServer,當有新的RegionServer加入的時候,HMaster須要分配一些Region到該RegionServer上去,讓其提供服務;當有RegionServer宕機時,HMaster須要將該RegionServer以前服務的Region都從新分配到當前正在提供服務的其它RegionServer上,以便不影響客戶端的正常訪問。下面是這種場景下使用ZooKeeper的基本步驟:

Master在ZooKeeper上建立/service/slaves結點,並設置對該結點的Watcher
每一個Slave在啓動成功後,建立惟一標識本身的臨時性(Ephemeral)結點/service/slaves/${slave_id},並將本身地址(ip/port)等相關信息寫入該結點
Master收到有新子結點加入的通知後,作相應的處理
若是有Slave宕機,因爲它所對應的結點是臨時性結點,在它的Session超時後,ZooKeeper會自動刪除該結點
Master收到有子結點消失的通知,作相應的處理
4. 簡單互斥鎖(Simple Lock) 
咱們知識,在傳統的應用程序中,線程、進程的同步,均可以經過操做系統提供的機制來完成。可是在分佈式系統中,多個進程之間的同步,操做系統層面就無能爲力了。這時候就須要像ZooKeeper這樣的分佈式的協調(Coordination)服務來協助完成同步,下面是用ZooKeeper實現簡單的互斥鎖的步驟,這個能夠和線程間同步的mutex作類比來理解:

多個進程嘗試去在指定的目錄下去建立一個臨時性(Ephemeral)結點 /locks/my_lock
ZooKeeper能保證,只會有一個進程成功建立該結點,建立結點成功的進程就是搶到鎖的進程,假設該進程爲A
其它進程都對/locks/my_lock進行Watch
當A進程再也不須要鎖,能夠顯式刪除/locks/my_lock釋放鎖;或者是A進程宕機後Session超時,ZooKeeper系統自動刪除/locks/my_lock結點釋放鎖。此時,其它進程就會收到ZooKeeper的通知,並嘗試去建立/locks/my_lock搶鎖,如此循環反覆
5. 互斥鎖(Simple Lock without Herd Effect) 
上一節的例子中有一個問題,每次搶鎖都會有大量的進程去競爭,會形成羊羣效應(Herd Effect),爲了解決這個問題,咱們能夠經過下面的步驟來改進上述過程:

每一個進程都在ZooKeeper上建立一個臨時的順序結點(Ephemeral Sequential) /locks/lock_${seq}
${seq}最小的爲當前的持鎖者(${seq}是ZooKeeper生成的Sequenctial Number)
其它進程都對只watch比它次小的進程對應的結點,好比2 watch 1, 3 watch 2, 以此類推
當前持鎖者釋放鎖後,比它次大的進程就會收到ZooKeeper的通知,它成爲新的持鎖者,如此循環反覆
這裏須要補充一點,一般在分佈式系統中用ZooKeeper來作Leader Election(選主)就是經過上面的機制來實現的,這裏的持鎖者就是當前的「主」。

6. 讀寫鎖(Read/Write Lock) 
咱們知道,讀寫鎖跟互斥鎖相比不一樣的地方是,它分紅了讀和寫兩種模式,多個讀能夠併發執行,但寫和讀、寫都互斥,不能同時執行行。利用ZooKeeper,在上面的基礎上,稍作修改也能夠實現傳統的讀寫鎖的語義,下面是基本的步驟:

每一個進程都在ZooKeeper上建立一個臨時的順序結點(Ephemeral Sequential) /locks/lock_${seq}
${seq}最小的一個或多個結點爲當前的持鎖者,多個是由於多個讀能夠併發
須要寫鎖的進程,Watch比它次小的進程對應的結點
須要讀鎖的進程,Watch比它小的最後一個寫進程對應的結點
當前結點釋放鎖後,全部Watch該結點的進程都會被通知到,他們成爲新的持鎖者,如此循環反覆
7. 屏障(Barrier) 
在分佈式系統中,屏障是這樣一種語義: 客戶端須要等待多個進程完成各自的任務,而後才能繼續往前進行下一步。下用是用ZooKeeper來實現屏障的基本步驟:

Client在ZooKeeper上建立屏障結點/barrier/my_barrier,並啓動執行各個任務的進程
Client經過exist()來Watch /barrier/my_barrier結點
每一個任務進程在完成任務後,去檢查是否達到指定的條件,若是沒達到就啥也不作,若是達到了就把/barrier/my_barrier結點刪除
Client收到/barrier/my_barrier被刪除的通知,屏障消失,繼續下一步任務
8. 雙屏障(Double Barrier)
雙屏障是這樣一種語義: 它能夠用來同步一個任務的開始和結束,當有足夠多的進程進入屏障後,纔開始執行任務;當全部的進程都執行完各自的任務後,屏障才撤銷。下面是用ZooKeeper來實現雙屏障的基本步驟:

進入屏障: Client Watch /barrier/ready結點, 經過判斷該結點是否存在來決定是否啓動任務 每一個任務進程進入屏障時建立一個臨時結點/barrier/process/${process_id},而後檢查進入屏障的結點數是否達到指定的值,若是達到了指定的值,就建立一個/barrier/ready結點,不然繼續等待 Client收到/barrier/ready建立的通知,就啓動任務執行過程 離開屏障: Client Watch /barrier/process,若是其沒有子結點,就能夠認爲任務執行結束,能夠離開屏障 每一個任務進程執行任務結束後,都須要刪除本身對應的結點/barrier/process/${process_id}

相關文章
相關標籤/搜索