最近加入了部門的技術興趣小組,被分配了Zookeeper的研究任務。在研究過程中,發現Zookeeper因爲其開源的特性和其卓越的性能特色,在業界使用普遍,有不少的應用場景,而這些不一樣的應用場景實際上底層的原理都是差很少的,只要你真正理解了Zookeeper的一些基礎概念和機制,就可以舉一反三。html
因而乎,在第一次和項目小組內成員分享過Zookeeper做爲服務註冊中心的原理和客戶端demo演示以後,我萌生出了整理一個專題的想法,以此爲起點,慢慢撿起本身的博客分享之路。java
本篇的內容主要介紹如下幾點:node
我最先接觸Zookeeper是由於咱們項目使用的微服務治理架構是Dubbo,Dubbo推薦使用的服務註冊中心就是Zookeeper。從本質上來講,Zookeeper就是一種分佈式協調服務,在分佈式環境中協調和管理服務是一個複雜的過程。ZooKeeper經過其簡單的架構和API解決了這個問題。 ZooKeeper容許開發人員專一於核心應用程序邏輯,而沒必要擔憂應用程序的分佈式特性。Zookeeper最先的應用是在Hadoop生態中,Apache HBase使用ZooKeeper跟蹤分佈式數據的狀態。shell
實際上從它的名字上就很好理解,Zoo - 動物園,Keeper - 管理員,動物園中有不少種動物,這裏的動物就能夠比做分佈式環境下多種多樣的服務,而Zookeeper作的就是管理這些服務。數據庫
ZooKeeper 的設計目標是將那些複雜且容易出錯的分佈式一致性服務封裝起來,構成一個高效可靠的原語集,並以一系列簡單易用的接口提供給用戶使用。apache
原語: 操做系統或計算機網絡用語範疇。是由若干條指令組成的,用於完成必定功能的一個過程。具備不可分割性·即原語的執行必須是連續的,在執行過程當中不容許被中斷。
Zookeeper提供服務主要就是經過:數據結構 + 原語集 + watch機制達到的。api
分佈式應用程序結合Zookeeper能夠實現諸如數據發佈/訂閱、負載均衡、命名服務、分佈式協調/通知、集羣管理、Master選舉、分佈式鎖和分佈式隊列等功能。服務器
從上圖能夠看到,Zookeeper的數據模型和Unix的文件系統目錄樹很相似,擁有一個層次的命名空間。這裏面的每個節點都被稱爲 - ZNode, 節點能夠擁有子節點,同時也容許少許數據節點存儲在該節點之下。(能夠理解成一個容許一個文件也能夠是一個目錄的文件系統)網絡
ZNode經過路徑引用,如同Unix中的文件路徑。路徑必須是絕對的,所以他們必須有斜槓字符/
來開頭,除此以外,路徑名必須是惟一的,且不能更改。session
這個特性在Dubbo的服務註冊上也有體現,Dubbo源碼中有個貫穿全局的類URL
,dubbo是以總線模式來時刻傳遞和保存配置信息的,也就是配置信息都被放在URL
上進行傳遞,隨時能夠取得相關配置信息。Dubbo在向註冊中心註冊時寫下的節點名就是由URL
中的URI
和配置信息編碼後組成的。以下圖。
這屬於這部分知識的擴展內容,在以後服務註冊中心的章節會更具體的說明。
前面提到過,ZNode兼具文件和目錄兩種特色,既像文件同樣維護着數據、元信息、ACL、時間戳等數據結構,又像目錄同樣能夠做爲路徑標識的一部分。
ZNode由如下幾部分組成:
Stat數據結構
操做控制列表(ACL) - 每一個節點都有一個ACL來作節點的操做控制,這個列表規定了用戶的權限,限定了特定用戶對目標節點的操做
版本 - ZNode有三個數據版本
Zxid
ZooKeeper的每一個節點維護者三個Zxid值,分別爲:cZxid、mZxid、pZxid。
下面有幾個須要注意的知識點着重講一下:
下圖是我在服務器上使用zkClient,用get命令獲取到的某個Dubbo微服務接口節點的狀態信息,來做爲示例,
[zk: localhost:2181(CONNECTED) 0] get /dubbo/com.***.microservice.ucs.api.UniqueControlApi 127.0.0.1 // 節點數據Data域 cZxid = 0xdd59 //Created ZXID,表示該ZNode被建立時的事務ID ctime = Thu Apr 18 15:17:11 CST 2019 //Created Time,表示該ZNode被建立的時間 mZxid = 0xdd59 //Modified ZXID,表示該ZNode最後一次被更新時的事務ID mtime = Thu Apr 18 15:17:11 CST 2019 //Modified Time,表示該節點最後一次被更新的時間 pZxid = 0xdd62 //表示該節點的子節點列表最後一次被修改時的事務ID。注意,只有子節點列表變動了纔會變動pZxid,子節點內容變動不會影響pZxid。 cversion = 4 //子節點的版本號 dataVersion = 0 //數據節點版本號 aclVersion = 0 //ACL版本號 ephemeralOwner = 0x0 //建立該節點的會話的sessionID。若是該節點是持久節點,那麼這個屬性值爲0。 dataLength = 9 // Data域內容長度 numChildren = 4 // 子節點個數 衆所周知,Dubbo接口子節點分爲providers/configurators/routers/consumers
關於Data域,Zookeeper中每一個節點存儲的數據要被原子性的操做,也就是說讀操做將獲取與節點相關的全部數據,寫操做也將替換掉節點的全部數據。
值得注意的是,Zookeeper雖然能夠存儲數據,可是從設計目的上,並非爲了作數據庫或者大數據存儲,相反,它是用來管理調度數據,好比分佈式應用中的配置文件信息、狀態信息、聚集位置等,這些數據一般是很小的數據,KB爲大小單位。ZNode對數據大小也有限制,至多1M。實際上從這裏,就能夠推導出Zookeeper用於分佈式配置中心的可行性。
在ZooKeeper中,能改變ZooKeeper服務器狀態的操做稱爲事務操做。通常包括數據節點建立與刪除、數據內容更新和客戶端會話建立與失效等操做。對應每個事務請求,ZooKeeper都會爲其分配一個全局惟一的事務ID,用Zxid表示。
由上圖的示例能夠看出,Zxid是一個64位的數字。前32位叫作epoch,用來標識Zookeeper 集羣中的Leader
節點,當Leader
節點更換時,就會有一個新的epoch。後32位則爲遞增序列。從這些Zxid中能夠間接地識別出ZooKeeper處理這些事務操做請求的全局順序。
ZNode節點類型嚴格來講有四種:持久節點、臨時節點、持久順序節點、臨時順序節點
/myapp
的znode建立爲順序節點,則ZooKeeper會將路徑更改成 /myapp0000000001
,並將下一個序列號設置爲0000000002
。若是兩個順序節點是同時建立的,那麼ZooKeeper不會對每一個znode使用相同的數字。順序節點在鎖定和同步中起重要做用。 如上圖,標明瞭Zookeeper服務的九種基本操做,進入ZkClient.sh
,使用help
,能夠看到這幾種操做。
[zk: localhost:2181(CONNECTED) 1] help ZooKeeper -server host:port cmd args stat path [watch] // 獲取指定節點的狀態信息 set path data [version] // setData操做 ls path [watch] // 查看某個節點下的全部子節點信息 delquota [-n|-b] path // 刪除節點配額 ls2 path [watch] // ls + stat 兩個命令結合 setAcl path acl // 設置ACL setquota -n|-b val path // 設置節點配額,-n 是限制子節點個數 -b是限制節點數據長度 history // 歷史命令 redo cmdno // 執行歷史命令 printwatches on|off delete path [version] // 刪除指定路徑節點,有子節點須要先刪除子節點 sync path // 同步視圖 listquota path // 查看節點配額信息 rmr path // 刪除節點及其子節點 get path [watch] // 獲取當前節點數據內容 create [-s] [-e] path data acl // 建立節點 addauth scheme auth quit getAcl path // 獲取ACL close connect host:port
從命令中能夠看到,更新ZooKeeper操做是有限制的。delete或setData必須明確要更新的Znode的版本號,咱們能夠調用exists找到。若是版本號不匹配,更新將會失敗。
更新ZooKeeper操做是非阻塞式的。所以客戶端若是失去了一個更新(因爲另外一個進程在同時更新這個Znode),他能夠在不阻塞其餘進程執行的狀況下,選擇從新嘗試或進行其餘操做。
在 ZooKeeper 中,一個客戶端鏈接是指客戶端和服務器之間的一個 TCP 長鏈接。客戶端啓動的時候,首先會與服務器創建一個 TCP 鏈接,從第一次鏈接創建開始,客戶端會話的生命週期也開始了。經過這個鏈接,客戶端可以經過心跳檢測與服務器保持有效的會話,也可以向Zookeeper服務器發送請求並接受響應,同時還可以經過該鏈接接收來自服務器的Watch事件通知。
客戶端以特定的時間間隔發送心跳以保持會話有效。若是ZooKeeper Server Ensembles在超過服務器開啓時指定的期間(會話超時)都沒有從客戶端接收到心跳,則它會斷定客戶端死機。
會話超時一般以毫秒爲單位。當會話因爲任何緣由結束時,在該會話期間建立的臨時節點也會被刪除。
在我看來,Watches - 監聽事件,是Zookeeper中一個很重要的特性,也是實現Zookeeper大多數功能的核心特性之一。簡單來講, Zookeeper容許Client端在指定節點上註冊Watches,在某些特定事件觸發的時候,Zookeeper服務端會將事件異步通知到感興趣(即註冊了Watches)的客戶端上去。能夠理解成一個訂閱/發佈系統,是否是。
Znode更改是與znode相關的數據的修改或znode的子項中的更改。只觸發一次watches。若是客戶端想要再次通知,則必須經過另外一個讀取操做來完成。當鏈接會話過時時,客戶端將與服務器斷開鏈接,相關的watches也將被刪除。
下面說完簡單的,來講點複雜的部分。
幾個特性先了解下:
Zookeeper的Watches 分爲兩種,數據監聽器(Data Watches)和子節點監聽器(Children Watches)。即你能夠對某個節點的Data設置watches,也能夠對某個子節點設置watches。
能夠看下Zookeeper Java 客戶端 Zkclient
中的設置watches的代碼:
// listener 監聽器 // path 節點路徑 // 子節點監聽器 private List<String> addTargetChildListener(String path, IZkChildListener listener) { return client.subscribeChildChanges(path, listener); } // 節點數據的監聽器 public void addChildDataListener(String path, IZkDataListener listener) { try { // 遞歸建立節點 client.subscribeDataChanges(path, listener); } catch (ZkNodeExistsException e) { } }
做爲開發者,須要知道監控節點的什麼操做會觸發你設置的watches。
再看下ZkClient中的數據監聽器接口IZkDataListener
public interface IZkDataListener { // 監控節點數據更新的時候會觸發 這段邏輯 public void handleDataChange(String dataPath, Object data) throws Exception; // 監控節點被刪除的時候會觸發 這段邏輯 public void handleDataDeleted(String dataPath) throws Exception; }
再看下ZkClient中的子節點監聽器接口IZkChildListener
public interface IZkChildListener { /** * Called when the children of the given path changed. * 監控節點的子節點列表改變時會觸發這段邏輯 * * @param parentPath * The parent path * @param currentChilds * The children or null if the root node (parent path) was deleted. * @throws Exception */ public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception; }
實際上看到這就能聯想到,Zookeeper是能夠當作分佈式配置中心來使用的,只不過你須要本身擴展他異步通知節點數據變化以後的邏輯,更新你的配置。在後面的章節會更新相關demo。
關於Watches 詳細介紹能夠參考官網的介紹:
ZooKeeper Watches
本章內容算是Zookeeper系列的開篇,介紹了Zookeeper的幾個基礎概念,而且給出了相關實例,助於理解。
如今咱們再回過頭來看看Zookeeper的特性:
① 順序一致性
從同一個客戶端發起的事務請求,最終將會嚴格按照其發起順序被應用到ZooKeeper中。② 原子性
全部事務請求的結果在集羣中全部機器上的應用狀況是一致的,也就是說要麼整個集羣全部集羣都成功應用了某一個事務,要麼都沒有應用,必定不會出現集羣中部分機器應用了該事務,而另一部分沒有應用的狀況。③ 單一視圖
不管客戶端鏈接的是哪一個ZooKeeper服務器,其看到的服務端數據模型都是一致的。④ 可靠性
一旦服務端成功地應用了一個事務,並完成對客戶端的響應,那麼該事務所引發的服務端狀態變動將會被一直保留下來,除非有另外一個事務又對其進行了變動。⑤ 實時性
一般人們看到實時性的第一反應是,一旦一個事務被成功應用,那麼客戶端可以當即從服務端上讀取到這個事務變動後的最新數據狀態。這裏須要注意的是,ZooKeeper僅僅保證在必定的時間段內,客戶端最終必定可以從服務端上讀取到最新的數據狀態。
今天的內容中,順序一致性是經過ZXid來實現的,全局惟一,順序遞增,同一個session中請求是FIFO的;可靠性的描述也能夠經過今天的知識進行理解,一次事務的應用,服務端狀態的變動會以Zxid、Znode數據版本、數據、節點路徑的形式保存下來。剩下的幾種特性是怎麼實現的,在學習完Zookeeper集羣相關的內容以後應該就能理解。
本篇文章中借鑑了網上幾篇優秀的文章,而且結合了我本人一些思考和實踐。但願能對你學習瞭解Zookeeper起到一些幫助。
下一章,我會介紹Zookeeper集羣方面的知識,CAP
理論在Zookeeper中的實踐,以及如何搭建Zookeeper的集羣。
[1] https://zookeeper.apache.org/... 官方文檔(強烈推薦)
[2] https://www.cnblogs.com/sundd... 做者應該是對官方文檔有比較深的瞭解,我發現他的文章的脈絡和官網有很類似的地方。寫的很是好
[3] https://www.jianshu.com/p/a17... 做者對Zookeeper作了一個易懂的整體介紹
[4] https://www.w3cschool.cn/zook... w3cSchool tutorial