Zookeeper服務

請戳GitHub原文: github.com/wangzhiwubi…node

大數據成神之路系列:

請戳GitHub原文: github.com/wangzhiwubi…git

Java高級特性加強-集合github

Java高級特性加強-多線程面試

Java高級特性加強-Synchronized編程

Java高級特性加強-volatile緩存

Java高級特性加強-併發集合框架bash

Java高級特性加強-分佈式服務器

Java高級特性加強-Zookeeper網絡

Java高級特性加強-JVMsession

Java高級特性加強-NIO

公衆號

  • 全網惟一一個從0開始幫助Java開發者轉作大數據領域的公衆號~

  • 公衆號大數據技術與架構或者搜索import_bigdata關注,大數據學習路線最新更新,已經有不少小夥伴加入了~

Zookeeper 服務

ZooKeeper 是一個高可用的高性能調度服務。這一節咱們將講述他的模型、操做和接口。

數據模型 Data Model

ZooKeeper包含一個樹形的數據模型,咱們叫作znode。一個znode中包含了存儲的數據和ACL(Access Control List)。ZooKeeper的設計適合存儲少許的數據,並不適合存儲大量數據,因此znode的存儲限制最大不超過1M。

數據的訪問被定義成原子性的。什麼是原子性呢?一個客戶端訪問一個znode時,不會只獲得一部分數據;客戶端訪問數據要麼得到所有數據,要麼讀取失敗,什麼也得不到。類似的,寫操做時,要麼寫入所有數據,要麼寫入失敗,什麼也寫不進去。ZooKeeper可以保證寫操做只有兩個結果,成功和失敗。絕對不會出現只寫入了一部分數據的狀況。與HDFS不一樣,ZooKeeper不支持字符的append(鏈接)操做。緣由是HDFS是被設計成支持數據流訪問(streaming data access)的大數據存儲,而ZooKeeper則不是。

咱們能夠經過path來定位znode,就像Unix系統定位文件同樣,使用斜槓來表示路徑。可是,znode的路徑只能使用絕對路徑,而不能想Unix系統同樣使用相對路徑,即Zookeeper不能識別../和./這樣的路徑。

節點的名稱是由Unicode字符組成的,除了zookeeper這個字符串,咱們能夠任意命名節點。爲何不能使用zookeeper命名節點呢?由於ZooKeeper已經默認使用zookeeper來命名了一個根節點,用來存儲一些管理數據。

請注意,這裏的path並非URIs,在Java API中是一個String類型的變量。

Ephemeral znodes

咱們已經知道,znode有兩種類型:ephemeral和persistent。在建立znode時,咱們指定znode的類型,而且在以後不會再被修改。當建立znode的客戶端的session結束後,ephemeral類型的znode將被刪除。persistent類型的znode在建立之後,就與客戶端沒什麼聯繫了,除非主動去刪除它,不然他會一直存在。Ephemeral znode沒有任何子節點。

雖然Ephemeral znode綁定了客戶端session,可是對任何其餘客戶端都是可見的,固然是在他們的ACL策略下容許訪問的狀況下。 當咱們在建立分佈式系統時,須要知道分佈式資源是否可用。Ephemeral znode就是爲這種場景應運而生的。正如咱們以前講述的例子中,使用Ephemeral znode來實現一個成員關係管理,任何一個客戶端進程任什麼時候候均可以知道其餘成員是否可用。 Znode的序號

若是在建立znode時,咱們使用排序標誌的話,ZooKeeper會在咱們指定的znode名字後面增長一個數字。咱們繼續加入相同名字的znode時,這個數字會不斷增長。這個序號的計數器是由這些排序znode的父節點來維護的。

若是咱們請求建立一個znode,指定命名爲/a/b-,那麼ZooKeeper會爲咱們建立一個名字爲/a/b-3的znode。咱們再請求建立一個名字爲/a/b-的znode,ZooKeeper會爲咱們建立一個名字/a/b-5的znode。ZooKeeper給咱們指定的序號是不斷增加的。Java API中的create()的返回結果就是znode的實際名字。

那麼序號用來幹什麼呢?固然是用來排序用的!後面《A Lock Service》中咱們將講述如何使用znode的序號來構建一個share lock。

觀察模式 Watches

觀察模式可使客戶端在某一個znode發生變化時獲得通知。觀察模式有ZooKeeper服務的某些操做啓動,並由其餘的一些操做來觸發。例如,一個客戶端對一個znode進行了exists操做,來判斷目標znode是否存在,同時在znode上開啓了觀察模式。若是znode不存在,這exists將返回false。若是稍後,另一個客戶端建立了這個znode,觀察模式將被觸發,將znode的建立事件通知以前開啓觀察模式的客戶端。咱們將在之後詳細介紹其餘的操做和觸發。

觀察模式只能被觸發一次。若是要一直得到znode的建立和刪除的通知,那麼就須要不斷的在znode上開啓觀察模式。在上面的例子中,若是客戶端還繼續須要得到znode被刪除的通知,那麼在得到建立通知後,客戶端還須要繼續對這個znode進行exists操做,再開啓一次觀察模式。

在《A Configuration Service》中,有一個例子將講述如何使用觀察模式在集羣中更新配置。

操做 Operations

下面的表格中列出了9種ZooKeeper的操做。

操做 說明 create Creates a znode (the parent znode must already exist) delete Deletes a znode (the znode must not have any children) exists Tests whether a znode exists and retrieves its metadata getACL, setACL Gets/sets the ACL for a znode getChildren Gets a list of the children of a znode getData,setData Gets/sets the data associated with a znode sync Synchronizes a client’s view of a znode with ZooKeeper

調用delete和setData操做時,咱們必須指定一個znode版本號(version number),即咱們必須指定咱們要刪除或者更新znode數據的哪一個版本。若是版本號不匹配,操做將會失敗。失敗的緣由多是在咱們提交以前,該znode已經被修改過了,版本號發生了增量變化。那麼咱們該怎麼辦呢?我能夠考慮重試,或者調用其餘的操做。例如,咱們提交更新失敗後,能夠從新獲取znode當前的數據,看看當前的版本號是什麼,再作更新操做。

ZooKeeper雖然能夠被看做是一個文件系統,可是因爲ZooKeeper文件很小,因此沒有提供像通常文件系統所提供的open、close或者seek操做。

注意 這裏的sync操做與POSIX文件系統的fsync()操做是不一樣的。就像咱們早前講過的,ZooKeeper的寫操做是原子性的,一個成功的寫操做只保證數據被持久化到大多數ZooKeeper的服務器存儲上。因此讀操做可能會讀取不到最新狀態的數據,sync操做用來讓client強制所訪問的ZooKeeper服務器上的數據狀態更新到最新狀態。咱們會在《一致性 Consistentcy》一節中詳細介紹。

批量更新 Multiupdate

ZooKeeper支持將一些原始的操做組合成一個操做單元,而後執行這些操做。那麼這種批量操做也是具備原子性的,只可能有兩種執行結果,成功和失敗。批量操做單元中的操做,不會出現一些操做執行成功,一些操做執行失敗的狀況,即要麼都成功,要麼都失敗。

Multiupdate對於綁定一些結構化的全局變量頗有用處。例如綁定一個無向圖(undirected graph)。無向圖的頂點(vertex)由znode來表示。添加和刪除邊(edge)的操做,由修改邊的兩個關聯znode來實現。若是咱們使用ZooKeeper的原始的操做來實現對邊(edge)的操做,那麼就有可能產生兩個znode修改不一致的狀況(一個修改爲功,一個修改失敗)。那麼咱們將修改兩個znode的操做放入到一個Multi修改單元中,就可以保證兩個znode,要麼都修改爲功,要麼都修改失敗。這樣就可以避免修改無向圖的邊時產生修改不一致的現象。

APIs

ZooKeeper客戶端使用的核心編程語言有JAVA和C;同時也支持Perl、Python和REST。執行操做的方式呢,分爲同步執行和異步執行。咱們以前已經見識過了同步的Java API中的exists。

public Stat exists(String path, Watcher watcher) throws KeeperException,
 InterruptedException
複製代碼

下面代碼則是異步方式的exists:

public void exists(String path, Watcher watcher, StatCallback cb, Object ctx)
複製代碼

Java API中,異步的方法的返回類型都是void,而操做的返回的結果將傳遞到回調對象的回調函數中。回調對象將實現StatCallback接口中的一個回調函數,來接收操做返回的結果。函數接口以下:

public void processResult(int rc, String path, Object ctx, Stat stat);
複製代碼

參數rc表示返回碼,請參考KeeperException中的定義。在stat參數爲null的狀況下,非0的值表示一種異常。參數path和ctx與客戶端調用的exists方法中的參數相等,這兩個參數一般用來肯定回調中得到的響應是來至於哪一個請求的。參數ctx能夠是任意對象,只有當path參數不能消滅請求的歧義時纔會用到。若是不須要參數ctx,能夠設置爲null。

應該使用同步API仍是異步API呢? 兩種API提供了相同的功能,須要使用哪一種API取決於你程序的模式。例如,你設計的程序模式是一個事件驅動模式的程序,那麼你最好使用異步API。異步API也能夠被用在追求一個比較好的數據吞吐量的場景。想象一下,若是你須要得去大量的znode數據,而且依靠獨立的進程來處理他們。若是使用同步API,每次讀取操做都會被阻塞住,直到返回結果。不如使用異步API,讀取操做能夠沒必要等待返回結果,繼續執行。而使用另外的線程來處理返回結果。

觀察模式觸發器 Watch triggers

讀操做,例如:exists、getChildren、getData會在znode上開啓觀察模式,而且寫操做會觸發觀察模式事件,例如:create、delete和setData。ACL(Access Control List)操做不會啓動觀察模式。觀察模式被觸發時,會生成一個事件,這個事件的類型取決於觸發他的操做: 1, exists啓動的觀察模式,由建立znode,刪除znode和更新znode操做來觸發。 2,getData啓動的觀察模式,由刪除znode和更新znode操做觸發。建立znode不會觸發,是由於getData操做成功的前提是znode必須已經存在。 3,getChildren啓動的觀察模式,由子節點建立和刪除,或者本節點被刪除時纔會被觸發。咱們能夠經過事件的類型來判斷是本節點被刪除仍是子節點被刪除:NodeChildrenChanged表示子節點被刪除,而NodeDeleted表示本節點刪除。

事件包含了觸發事件的znode的path,因此咱們經過NodeCreated和NodeDeleted事件就能夠知道哪一個znode被建立了或者刪除了。若是咱們須要在NodeChildrenChanged事件發生後知道哪一個子節點被改變了,咱們就須要再調用一次getChildren來得到一個新的子節點列表。與之相似,在NodeDataChanged事件發生後,咱們須要調用getData來得到新的數據。咱們在編寫程序時,會在接收到事件通知後改變znode的狀態,因此咱們必定要清楚的記住znode的狀態變化。

ACLs 訪問控制操做

znode的建立時,咱們會給他一個ACL(Access Control List),來決定誰能夠對znode作哪些操做。 ZooKeeper經過鑑權來得到客戶端的身份,而後經過ACL來控制客戶端的訪問。鑑權方式有以下幾種:

  • digest 使用用戶名和密碼方式

  • sasl 使用Kerberos鑑權

  • ip 使用客戶端的IP來鑑權

客戶端能夠在與ZooKeeper創建會話鏈接後,本身給本身受權。受權是並非必須的,雖然znode的ACL要求客戶端必須是身份合法的,在這種狀況下,客戶端能夠本身受權來訪問znode。下面的例子,客戶端使用用戶名和密碼爲本身受權:

zk.addAuthInfo("digest", "tom:secret".getBytes());
複製代碼

ACL是由鑑權方式、鑑權方式的ID和一個許可(permession)的集合組成。例如,咱們想經過一個ip地址爲10.0.0.1的客戶端訪問一個znode。那麼,咱們須要爲znode設置一個ACL,鑑權方式使用IP鑑權方式,鑑權方式的ID爲10.0.0.1,只容許讀權限。使用JAVA咱們將像以下方式建立一個ACL對象:

new ACL(Perms.READ,new Id("ip", "10.0.0.1"));
複製代碼

全部的許可權限將在下表中列出。請注意,exists操做不受ACL的控制,因此任何一個客戶端均可以經過exists操做來得到任何znode的狀態,從而得知znode是否真的存在。

在ZooDefs.Ids類中,有一些ACL的預約義變量,包括OPEN_ACL_UNSAFE,這個設置表示將賦予全部的許可給客戶端(除了ADMIN的許可)。

另外,咱們可使用ZooKeeper鑑權的插件機制,來整合第三方的鑑權系統。

實現 Implementation

ZooKeeper服務能夠在兩種模式下運行。在standalone模式下,咱們能夠運行一個單獨的ZooKeeper服務器,咱們能夠在這種模式下進行基本功能的簡單測試,可是這種模式沒有辦法體現ZooKeeper的高可用特性和快速恢復特性。在生產環境中,咱們通常採用replicated(複製)模式安裝在多臺服務器上,組建一個叫作ensemble的集羣。ZooKeeper在他的副本之間實現高可用性,而且只要ensemble集羣中可以推舉出主服務器,ZooKeeper的服務就能夠一直不終斷。例如,在一個5個節點的ensemble中,容忍有2個節點脫離集羣,服務仍是可用的。由於剩下的3個節點投票,能夠產生超過集羣半數的投票,來推選一臺主服務器。而6個節點的ensemble中,也只能容忍2個節點的服務器死機。由於若是3個節點脫離集羣,那麼剩下的3個節點不管如何不能產生超過集羣半數的投票來推選一個主服務器。因此,通常狀況下ensemble中的服務器數量都是奇數。

從概念上來看,ZooKeeper實際上是很簡單的。他所作的一切就是保證每一次對znode樹的修改,都可以複製到ensemble的大多數服務器上。若是非主服務器脫離集羣,那麼至少有一臺服務器上的副本保存了最新狀態。剩下的其餘的服務器上的副本,會很快更新這個最新的狀態。

爲了實現這個簡單而不平凡的設計思路,ZooKeeper使用了一個叫作Zab的協議。這個協議分爲兩階段,而且不斷的運行在ZooKeeper上:

階段 1:領導選舉(Leader election) Ensemble中的成員經過一個程序來選舉出一個首領成員,咱們叫作leader。其餘的成員就叫作follower。在大多數(quorum)follower完成與leader狀態同步時,這個階段才結束。

階段 2: 原子廣播(Atomic broadcast) 全部的寫入請求都會發送給leader,leader在廣播給follower。當大多數的follower已經完成了數據改變,leader纔會將更新提交,客戶端就會隨之獲得leader更新成功的消息。協議中的設計也是具備原子性的,因此寫入操做只有成功和失敗兩個結果。

若是leader脫離了集羣,剩下的節點將選舉一個新的leader。若是以前的leader回到了集羣中,那麼將被視做一個follower。leader的選舉很快,大概200ms就可以產生結果,因此不會影響執行效率。 Ensemble中的全部節點都會在更新內存中的znode樹的副本以前,先將更新數據寫入到硬盤上。讀操做能夠請求任何一臺ZooKeeper服務器,並且讀取速度很快,由於讀取是內存中的數據副本。

數據一致性 Consistency

理解了ZooKeeper的實現原理,有助於理解ZooKeeper如何保證數據的一致性。就像字面上理解的「leader」和「follower」的意思同樣,在ensemble中follower的update操做會滯後於leader的update完成。事實的結果使咱們在提交更新數據以前,沒必要在每一臺ZooKeeper服務器上執行持久化變動數據,而是僅需在主服務器上執行持久化變動數據。ZooKeeper客戶端的最佳實踐是所有連接到follower上。然而客戶端是有可能鏈接到leader上的,而且客戶端控制不了這個選擇,甚至客戶端並不知道鏈接到了follower仍是leader。下圖所示,讀操做向follower請求便可,而寫操做由leader來提交。

每個對znode樹的更新操做,都會被賦予一個全局惟一的ID,咱們稱之爲zxid(ZooKeeper Transaction ID)。更新操做的ID按照發生的時間順序升序排序。例如,例如z1小於z2,那麼z1的操做就早於z2的操做。

ZooKeeper在數據一致性上實現了以下幾個方面:

順序一致性 從客戶端提交的更新操做是按照前後循序排序的。例如,若是一個客戶端將一個znode z賦值爲a,而後又將z的值改變成b,那麼在這個過程當中不會有客戶端在z的值變爲b後,取到的值是a。

原子性 更新操做的結果不是失敗就是成功。即,若是更新操做失敗,其餘的客戶端是不會知道的。

系統視圖惟一性 不管客戶端鏈接到哪一個服務器,都將看見惟一的系統視圖。若是客戶端在同一個會話中去鏈接一個新的服務器,那麼他所看見的視圖的狀態不會比以前服務器上看見的更舊。當ensemble中的一個服務器宕機,客戶端去嘗試鏈接另一臺服務器時,若是這臺服務器的狀態舊於以前宕機的服務器,那麼服務器將不會接受客戶端的鏈接請求,直到服務器的狀態遇上以前宕機的服務器爲止。

持久性 一旦更新操做成功,數據將被持久化到服務器上,而且不能撤銷。因此服務器宕機重啓,也不會影響數據。 時效性

系統視圖的狀態更新的延遲時間是有一個上限的,最多不過幾十秒。若是服務器的狀態落後於其餘服務器太多,ZooKeeper會寧肯關閉這個服務器上的服務,強制客戶端去鏈接一個狀態更新的服務器。

從執行效率上考慮,讀操做的目標是內存中的緩存數據,而且讀操做不會參與到寫操做的全局排序中。這就會引發客戶端在讀取ZooKeeper的狀態時產生不一致。例如,A客戶端將znode z的值由a改變成a1,而後通知客戶端B去讀取z的值,可是B讀取到的值是a,而不是修改後的a1,爲了阻止這種狀況出現,B在讀取z的值以前,須要調用sync方法。sync方法會強制B鏈接的服務器狀態與leader的狀態同步,這樣B在讀取z的值就是A從新更改過的值了。

sync操做只在異步調用時纔可用,緣由是你不須要等待操做結束再去執行其餘的操做。所以,ZooKeeper保證全部的子操做都會在sync結束後再執行,甚至在sync操做以前發出的操做請求也不例外。

會話 Sessions

ZooKeeper的客戶端中,配置了一個ensemble服務器列表。當啓動時,首先去嘗試鏈接其中一個服務器。若是嘗試鏈接失敗,那麼會繼續嘗試鏈接下一個服務器,直到鏈接成功或者所有嘗試鏈接失敗。

一旦鏈接成功,服務器就會爲客戶端建立一個會話(session)。session的過時時間由建立會話的客戶端應用來設定,若是在這個時間期間,服務器沒有收到客戶端的任何請求,那麼session將被視爲過時,而且這個session不能被從新建立,而建立的ephemeral znode將隨着session過時被刪除掉。在會話長期存在的狀況下,session的過時事件是比較少見的,可是應用程序如何處理好這個事件是很重要的。(咱們將在《The Resilient ZooKeeper Application》中詳細介紹) 在長時間的空閒狀況下,客戶端會不斷的發送ping請求來保持session。(ZooKeeper的客戶端開發工具的liberay實現了自動發送ping請求,因此咱們沒必要去考慮如何維持session)ping請求的間隔被設置成足夠短,以便可以及時發現服務器失敗(由讀操做的超時時長來設置),而且可以及時的在session過時前鏈接到其餘服務器上。 容錯鏈接到其餘服務器上,是由ZooKeeper客戶端自動完成的。重要的是在鏈接到其餘服務器上後,以前的session以及epemeral節點還保持可用狀態。 在容錯的過程當中,應用將收到與服務斷開鏈接和鏈接的通知。Watch模式的通知在斷開連接時,是不會發送斷開鏈接事件給客戶端的,斷開鏈接事件是在從新鏈接成功後發送給客戶端的。若是在從新鏈接到其餘節點時,應用嘗試一個操做,這個操做是必定會失敗的。對於這一點的處理,是一個ZooKeeper應用的重點。

時間 Time

在ZooKeeper中有一些時間的參數。tick是ZooKeeper的基礎時間單位,用來定義ensemble中服務器上運行的程序的時間表。其餘時間相關的配置都是以tick爲單位的,或者以tick的值爲最大值或者最小值。例如,session的過時時間在2 ticks到20 ticks之間,那麼你再設置時選擇的session過時時間必須在2和20之間的一個數。

一般狀況1 tick等於2秒。那麼就是說session的過時時間的設置範圍在4秒到40秒之間。在session過時時間的設置上有一些考慮。過時時間過短會形成加快物理失敗的監測頻率。在組成員關係的例子中,session的過時時間與從組中移除失敗的成員花費的時間相等。若是設置太低的session過時時間,那麼網絡延遲就有可能形成非預期的session過時。這種狀況下,就會出如今短期內一臺機器不斷的離開組,而後又重新加入組中。

若是應用須要建立比較複雜的臨時狀態,那麼就須要較長的session過時時間,由於重構花費的時間比較長。有一些狀況下,須要在session的生命週期內重啓,並且要保證重啓完後session不過時(例如,應用維護和升級的狀況)。服務器會給每個session一個ID和密碼,若是在鏈接建立時,ZooKeeper驗證經過,那麼session將被恢復使用(只要session沒過時就行)。因此應用程序能夠實現一個優雅的關機動做,在重啓以前,將session的ID和密碼存儲在一個穩定的地方。重啓以後,經過ID和密碼恢復session。

這僅僅是在一些特殊的狀況下,咱們須要使用這個特性來使用比較長的session過時時間。大多數狀況下,咱們仍是要考慮當出現非預期的異常失敗時,如何處理session過時,或者僅須要優雅的關閉應用,在session過時前不用重啓應用。

一般狀況也越大規模的ensemble,就須要越長的session過時時間。Connetction Timeout、Read Timeout和Ping Periods都由一個以服務器數量爲參數的函數計算獲得,當ensemble的規模擴大,這些值須要逐漸減少。若是爲了解決常常失去鏈接而須要增長timeout的時長,建議你先監控一下ZooKeeper的metrics,再去調整。

狀態 States

ZooKeeper對象在他的生命週期內會有不一樣的狀態,咱們經過getState()來得到當前的狀態。

public States getState()
複製代碼

狀態是一個枚舉類型的數據。新構建的ZooKeeper對象在嘗試鏈接ZooKeeper服務時的狀態是CONNECTING,一旦與服務創建了鏈接那麼狀態就變成了CONNECTED。

客戶端能夠經過註冊一個觀察者對象來接收ZooKeeper對象狀態的遷移。當經過CONNECTED狀態後,觀察者將接收到一個WatchedEvent事件,他的屬性KeeperState的值是SyncConnected。

觀察者有兩個職能:一是接收ZooKeeper的狀態改變通知;二是接收znode的改變通知。ZooKeeper對象構造時傳遞進去的watcher對象,默認是用來接收狀態改變通知的,可是znode的改變通知也可能會共享使用默認的watcher對象,或者使用一個專用的watcher。咱們能夠經過一個Boolean變量來指定是否使用共享默認watcher。

ZooKeeper實例會與服務鏈接斷開或者從新鏈接,狀態會在CONNECTING和CONNECTED之間轉換。若是鏈接斷開,watcher會收到一個斷開鏈接事件。請注意,這兩個狀態都是ZooKeeper實例本身初始化的,而且在斷開鏈接後會自動進行重鏈接。

若是調用了close()或者session過時,ZooKeeper實例會轉換爲第三個狀態CLOSED,此時在接受事件的KeeperState屬性值爲Expired。一旦ZooKeeper的狀態變爲CLOSED,說明實例已經不可用(能夠經過isAlive()來判斷),而且不能再被使用。若是要從新創建鏈接,就須要從新構建一個ZooKeeper實例。

GitHub: github.com/wangzhiwubi…

關注公衆號,內推,面試,資源下載,關注更多大數據技術~
                   預計更新500+篇文章,已經更新50+篇~ 
複製代碼
相關文章
相關標籤/搜索