Zookeeper學習系列【一】 教會你Zookeeper的一些基礎概念
前言
最近加入了部門的技術興趣小組,被分配了Zookeeper的研究任務。在研究過程當中,發現Zookeeper由於其開源的特性和其卓越的效能特點,在業界使用廣泛,有很多的應用場景,而這些不同的應用場景實際上底層的原理都是差不多的,只要你真正理解了Zookeeper的一些基礎概念和機制,就能夠觸類旁通。
於是乎,在第一次和專案小組內成員分享過Zookeeper作為服務註冊中心的原理和客戶端demo演示之後,我萌生出了整理一個專題的想法,以此為起點,慢慢撿起自己的部落格分享之路。
本篇的內容主要介紹以下幾點:
- What is Zookeeper
- Zookeeper 資料模型
- Zookeeper 服務基本操作
- Sessions
- Watcher
- 總結
一、What is Zookeeper
我最早接觸Zookeeper是因為我們專案使用的微服務治理架構是Dubbo,Dubbo推薦使用的服務註冊中心就是Zookeeper。從本質上來說,Zookeeper就是一種分散式協調服務,在分散式環境中協調和管理服務是一個複雜的過程。ZooKeeper通過其簡單的架構和API解決了這個問題。 ZooKeeper允許開發人員專注於核心應用程式邏輯,而不必擔心應用程式的分散式特性。Zookeeper最早的應用是在Hadoop生態中,Apache HBase使用ZooKeeper跟蹤分散式資料的狀態。
實際上從它的名字上就很好理解,Zoo - 動物園,Keeper - 管理員,動物園中有很多種動物,這裡的動物就可以比作分散式環境下多種多樣的服務,而Zookeeper做的就是管理這些服務。
ZooKeeper 的設計目標是將那些複雜且容易出錯的分散式一致性服務封裝起來,構成一個高效可靠的原語集,並以一系列簡單易用的介面提供給使用者使用。
原語: 作業系統或計算機網路用語範疇。是由若干條指令組成的,用於完成一定功能的一個過程。具有不可分割性·即原語的執行必須是連續的,在執行過程中不允許被中斷。
Zookeeper提供服務主要就是通過:資料結構 + 原語集 + watcher機制達到的。
分散式應用程式結合Zookeeper可以實現諸如 資料釋出/訂閱、負載均衡、命名服務、分散式協調/通知、叢集管理、Master選舉、分散式鎖和分散式佇列 等功能。
二、Zookeeper 資料模型
ZNode
從上圖可以看到,Zookeeper的資料模型和Unix的檔案系統目錄樹很類似,擁有一個層次的名稱空間。這裡面的每一個節點都被稱為 - ZNode, 節點可以擁有子節點,同時也允許少量資料節點儲存在該節點之下。(可以理解成一個允許一個檔案也可以是一個目錄的檔案系統)
(1)節點引用方式
ZNode通過路徑引用,如同Unix中的檔案路徑。路徑必須是絕對的,因此他們必須有斜槓字元 /
來開頭,除此之外,路徑名必須是唯一的,且不能更改。
這個特性在Dubbo的服務註冊上也有體現,Dubbo原始碼中有個貫穿全域性的類 URL
,dubbo是以匯流排模式來時刻傳遞和儲存配置資訊的,也就是配置資訊都被放在 URL
上進行傳遞,隨時可以取得相關配置資訊。Dubbo在向註冊中心註冊時寫下的節點名就是由 URL
中的 URI
和配置資訊編碼後組成的。如下圖。
這屬於這部分知識的擴充套件內容,在之後服務註冊中心的章節會更具體的說明。
(2)ZNode結構
前面提到過,ZNode兼具檔案和目錄兩種特點,既像檔案一樣維護著資料、元資訊、ACL、時間戳等資料結構,又像目錄一樣可以作為路徑標識的一部分。
ZNode由以下幾部分組成:
-
Stat資料結構
-
操作控制列表(ACL) - 每個節點都有一個ACL來做節點的操作控制,這個列表規定了使用者的許可權,限定了特定使用者對目標節點的操作
- CREATE - 建立子節點的許可權
- READ - 獲取節點資料和子節點列表的許可權
- WRITE - 更新節點資料的許可權
- DELETE - 刪除子節點的許可權
- ADMIN - 設定節點ACL的許可權
-
版本 - ZNode有三個資料版本
- version - 當前ZNode的版本
- cversion - 當前ZNode子節點的版本
- aversion - 當前ACL列表的版本
-
Zxid
- 可以理解成Zookeeper中 時間戳的一種表現形式 ,也可以理解成 事務ID 的概念
- 如果Zxid1的值小於Zxid2的值,那麼Zxid1所對應的事件發生在Zxid2所對應的事件之前。
-
ZooKeeper的每個節點維護者三個Zxid值,分別為: cZxid、mZxid、pZxid 。
- cZxid :節點建立時間 create
- mZxid :節點最近一次修改時間 modify
- pZxid :該節點的子節點列表最後一次被修改時的時間,子節點內容變更不會變更pZxid
-
- data域
- children節點
下面有幾個需要注意的知識點著重講一下:
A. 狀態資訊/節點屬性
下圖是我在伺服器上使用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
B. Data域
關於Data域,Zookeeper中每個節點儲存的資料要被 原子性的操作 ,也就是說讀操作將獲取與節點相關的所有資料,寫操作也將替換掉節點的所有資料。
值得注意的是,Zookeeper雖然可以儲存資料,但是 從設計目的上,並不是為了做資料庫或者大資料儲存,相反,它是用來管理排程資料,比如分散式應用中的配置檔案資訊、狀態資訊、彙集位置等 ,這些資料通常是很小的資料,KB為大小單位。ZNode對資料大小也有限制,至多1M。實際上從這裡,就可以推匯出Zookeeper用於分散式配置中心的可行性。
C. Zxid
在ZooKeeper中, 能改變ZooKeeper伺服器狀態的操作稱為事務操作。一般包括資料節點建立與刪除、資料內容更新和客戶端會話建立與失效等操作 。對應每一個事務請求,ZooKeeper都會為其分配一個全域性唯一的 事務ID ,用Zxid表示。
由上圖的示例可以看出,Zxid是一個64位的數字。 前32位 叫做epoch,用來 標識Zookeeper 叢集中的 Leader
節點,當 Leader
節點更換時,就會有一個新的epoch 。 後32位 則為遞增序列。從這些Zxid中可以間接地識別出ZooKeeper處理這些事務操作請求的全域性順序。
(3)節點型別
ZNode節點型別嚴格來說有四種: 持久節點、臨時節點、持久順序節點、臨時順序節點
- PERSISTENT 持久節點 - 該節點的生命週期不依賴於session,建立之後客戶端斷開連線,節點依舊存在,只用客戶端執行刪除操作,節點才能被刪除;
- EPHEMERAL 臨時節點 - 該節點的宣告週期依賴於session,客戶端斷開連線,臨時節點就會自動刪除。另外, 臨時節點不允許有子節點。
- SEQUENTIAL 順序節點 - 當選擇建立順序節點時,ZooKeeper通過將10位的序列號附加到原始名稱來設定znode的路徑。例如,如果將具有路徑
/myapp
的znode建立為順序節點,則ZooKeeper會將路徑更改為/myapp0000000001
,並將下一個序列號設定為0000000002
。如果兩個順序節點是同時建立的,那麼ZooKeeper不會對每個znode使用相同的數字。 順序節點在鎖定和同步中起重要作用。
三、Zookeeper服務基本操作
如上圖,標明瞭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),他可以在不阻塞其他程序執行的情況下,選擇重新嘗試或進行其他操作。
四、Sessions
在 ZooKeeper 中,一個客戶端連線是指客戶端和伺服器之間的一個 TCP 長連線 。客戶端啟動的時候,首先會與伺服器建立一個 TCP 連線,從第一次連線建立開始,客戶端會話的生命週期也開始了。 通過這個連線,客戶端能夠通過心跳檢測與伺服器保持有效的會話,也能夠向Zookeeper伺服器傳送請求並接受響應,同時還能夠通過該連線接收來自伺服器的Watch事件通知。
客戶端以特定的時間間隔傳送心跳以保持會話有效。如果ZooKeeper Server Ensembles在超過伺服器開啟時指定的期間(會話超時)都沒有從客戶端接收到心跳,則它會判定客戶端宕機。
會話超時通常以毫秒為單位。當會話由於任何原因結束時,在該會話期間建立的臨時節點也會被刪除。
五、Watches
在我看來, Watches - 監聽事件 ,是Zookeeper中一個很重要的特性,也是實現Zookeeper大多數功能的核心特性之一。簡單來說, Zookeeper允許Client端在指定節點上註冊Watches,在某些特定事件觸發的時候,Zookeeper服務端會將事件 非同步 通知到感興趣(即註冊了Watches)的客戶端上去 。可以理解成一個訂閱/釋出系統,是不是。
Znode更改是與znode相關的資料的修改或znode的子項中的更改。只觸發一次watches。如果客戶端想要再次通知,則必須通過另一個讀取操作來完成。當連線會話過期時,客戶端將與伺服器斷開連線,相關的watches也將被刪除。
下面說完簡單的,來說點複雜的部分。
幾個特性先了解下:
- One-time trigger 一次watch時間只會被觸發一遍,如果節點再次發生變化,除非之前有重新設定過watches,不然會收到通知;
- Sent to Client 當watch的物件狀態發生改變時,將會觸發此物件上watch所對應的事件。watch事件將被非同步地傳送給客戶端,並且ZooKeeper為watch機制提供了 有序的一致性保證(Ordering guarantee) 。
- The data for which the watch was set 傳送給客戶端的資料資訊,實際上就是你這個watch監視的型別,見下文介紹
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。
- 一個成功的setData操作將觸發Znode的資料watches
- 一個成功的create操作將觸發Znode的資料watches以及子節點watches
- 一個成功的delete操作將觸發Znode的資料watches和子節點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系列的開篇,介紹了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 加粗文字