一、簡述
在一羣動物掌管的世界中,動物沒有人類聰明的思想,爲了保持動物世界的生態平衡,這時,動物管理員—zookeeper誕生了
打開Apache zookeeper的官網,一句話定義zookeeper:Apache ZooKeeper致力於開發和維護可實現高度可靠的分佈式協調的開源服務器。
zookeeper是個服務,服務的對象我們都成爲客戶端,在大數據生態裏面的客戶,hadoop、hbase、hive…組件都是分佈式部署,這些組件們利用zookeeper的服務做了一些維持自身平衡的事情,比如集羣管理、master選舉、消息發佈訂閱、數據存儲、分佈式鎖等等。作爲整個集羣的心臟的存在,zookeeper本身也是分佈式,只有在分佈式的基礎上才能實現高度可靠,否則任何單點的可靠都是在耍流氓。
二、基本概念
1.數據結構
直觀的一看,zk的數據結構類似一顆樹,所有的節點都稱爲znode,所有帶子節點的znode也就是圖中的圈圈,我們都可以通俗理解爲linux系統的目錄,最底層的znode,也就是圖中的菱形,可以理解爲文件。通過zookeeper可視化客戶端ZooInspector可以清晰地看到裏面的目錄文件結構
通過客戶端,我們既能直觀的看到zk內部的目錄結構,可以直觀的判斷哪些組件用到了zk,看到一些熟悉的身影。
2.Znode你應該瞭解的
(1)節點類型
Znode 分爲兩種:
- 臨時節點:該節點的生命週期依賴於創建它們的會話。一旦會話結束,臨時節點將被自動刪除,當然可以也可以手動刪除。 臨時節點不允許擁有子節點。
- 永久節點:該節點的生命週期不依賴於會話,並且只有在客戶端顯示執行刪除操作的時候,他們才能被刪除。
Znode還有一個有序的特性,直白的說,當你創建一個節點名的時候,zk會總動在節點名後面加一串10位的數字,比如0000000001,來操作一遍。在安裝了zk機器上執行zookeeper-client進入zk命令行
# -s表示創建有順序節點,這裏創建的節點是/diaozhatian,創建的znode名自動添加了後綴序號0000000037
[zk: localhost:2181(CONNECTED) 2] create -s /diaozhatian aaaaaa
Created /diaozhatian0000000037
# 所以獲取數值的時候也要用全名/diaozhatian0000000037獲取
[zk: localhost:2181(CONNECTED) 5] get /diaozhatian0000000037
aaaaaa
#下面是默認情況下創建永久節點和獲取數據的方法
[zk: localhost:2181(CONNECTED) 3] create /diaozhadi bbbbbbbb
Created /diaozhadi
[zk: localhost:2181(CONNECTED) 6] get /diaozhadi
bbbbbbbb
這樣便會存在四種類型的 Znode 節點, 分別對應:
Znode節點類型 | 說明 |
---|---|
PERSISTENT | 永久節點 |
EPHEMERAL | 臨時節點 |
PERSISTENT_SEQUENTIAL | 有序的永久節點 |
EPHEMERAL_SEQUENTIAL | 有序的臨時節點 |
(2)節點屬性
每個 znode 都包含了一系列的屬性,通過命令 get, 可以獲得節點的屬性。
# -s -e 參數表示創建一個有序的臨時節點
[zk: localhost:2181(CONNECTED) 7] create -s -e /niubility 666
Created /niubility0000000039
# 獲取數據和屬性
[zk: localhost:2181(CONNECTED) 8] get /niubility0000000039
666 # 節點數據
cZxid = 0x70159 # Znode 創建的事務 id
ctime = Sun Apr 05 10:59:45 CST 2020 # 節點創建時的時間戳
mZxid = 0x70159 # Znode 被修改的事務 id
mtime = Sun Apr 05 10:59:45 CST 2020 # 節點最新一次更新發生時的時間戳
pZxid = 0x70159 # 該節點的子節點(或該節點)的最近一次 創建/刪除對應
cversion = 0 # 子節點的版本號。當 znode 的子節點有變化時, cversion 的值就會增加 1
dataVersion = 0 # 數據版本號,每次對節點進行 set 操作, dataVersion 的值都會增加 1
aclVersion = 0 # ACL 的版本號
ephemeralOwner = 0x17147dcc5e3001d # 如果該節點爲臨時節點, ephemeralOwner 值表示與該節點綁定的 session id. 如果不是, ephemeralOwner 值爲 0
dataLength = 3 # 數據長度,上面是666,所以這裏是3
numChildren = 0 # 子節點個數
三、基本功能
1.文件系統
文件系統是幹嘛的?存數據用的。zk既然有文件系統的功能,自然就少不了數據的存儲和管理。zk和 linux 的文件系統很像,也是樹狀,這樣就可以確定每個路徑都是唯一的。但是Znode又跟linux不一樣,嚴格來說,zk的帶有子節點的Znode不能當成目錄,因爲zk的所有Znode不管父子節點都能帶數據,但linux的目錄不能,所以zk裏面的路徑不能叫文件夾也不能叫文件,統一稱爲Znode,上代碼來比較直觀的理解下:
[zk: localhost:2181(CONNECTED) 2] create /father zhangsan
Created /father
[zk: localhost:2181(CONNECTED) 4] create /father/child lisi
Created /father/child
[zk: localhost:2181(CONNECTED) 5] get /father
zhangsan
[zk: localhost:2181(CONNECTED) 6] get /father/child
lisi
可以清晰地看到不論是父子節點,都可以存放數據。要注意的是,Zookeeper Server啓動時會把所有Znode加載進內存,所以zk不適合存儲太大的數據,否則造成oom,作爲心臟停止跳動,後果不堪設想!後面會專門寫一篇文章記錄一次線上事故
zookeeper的文件系統的特點:
- zk的文件系統和Linux的文件系統目錄結構一樣,從”/“開始
- zk的訪問路徑只有絕對路徑,沒有相對路徑。
- zk中沒有文件和目錄的概念,只有znode節點,Znode既有文件的功能,又有目錄的功能
2.集羣管理
在大數據生態中,各種各樣的集羣需要zk維護,zk做集羣管理的功能無非就兩個:
(1) 節點的加入和退出
首先,在zk中創建持久節點 /GroupMembers 作爲父節點,父節點監聽子節點的變化,client通過註冊到zk,會在/GroupMembers下創建臨時節點,臨時節點的特性時當客戶端與zk斷開連接時會自動刪除,不論是創建和刪除,zk server都會監聽,通過Watcher監聽與通知機制,並且會把變動的結果通知到到其它client,這樣,其它所有client都知道了其它兄弟們的存活情況
(2) Master的選舉
有很多應用都是master-slave架構,master必須7*24小時工作,但是每次只能一個master在工作,這時需要一個選舉機制,確定一個active狀態的master,當active的master出現故障掛掉的時候,standby的master頂上去,這裏主要講zk這端的實現。Master選舉的功能,其實就是各個master註冊到zk的目錄下,創建臨時節點,成功在zk創建臨時節點的目錄,也就被選舉爲Master,其它備用的Master就會收到監聽zk裏面臨時節點的變化,一旦刪除,就會爭奪創建臨時節點的權利成爲新的Master。
本質其實是利用zookeeper的臨時節點的特性:臨時節點隨着會話的消亡二消亡,同一個臨時節點只能創建一個,創建失敗的節點(從master)對創建成功節點(主master)進行監控,一旦創建成功的節點(主master)會話消失,之前創建失敗的節點(從master)就會監聽到去搶奪創建臨時節點
如上圖,master1和master2爲爭奪上位之戰,都試圖在zk裏面創建一個臨時的Znode,但是同一個路徑只能創建一次,先創建的那個被選舉爲Master,未創建成功的則訂閱監聽Znode是否存在,如果不存在,則上位成Master。上圖用的是排他鎖,共享鎖用的是後面10位數字最小的那個作爲獲取鎖的節點,原理都差不多,先到先得的原則。下面會具體講這兩種鎖。
3.分佈式鎖機制
鎖的定義:用於多線程環境下控制只有一個線程可以訪問某一個資源,不能多個線程同時訪問,鎖旨在強制實施互斥排他、併發控制策略。
分佈式鎖的定義:在分佈式系統中,應用可能部署在多臺機器上,應用不在同一個jvm裏面,這個時候還需要維護一個互斥排他的策略,需要一個跨jvm的鎖同步,爲了解決跨系統、跨jvm的鎖,我們就必須引入分佈式鎖。
這裏要搞清一個概念,所謂鎖,並不是一個實際的對象或代碼層面抽象存在的一種東西,鎖是一種策略,這裏初學者很容易搞混
(1) 排他鎖
排他鎖,又稱寫鎖或獨佔鎖。如果事務T1對數據對象O1加上了排他鎖,那麼在整個加鎖期間,只允許事務T1對O1進行讀取或更新操作,其他任務事務都不能對這個數據對象進行任何操作,直到T1釋放了排他鎖。
排他鎖核心是保證當前有且僅有一個事務獲得鎖,並且鎖釋放之後,所有正在等待獲取鎖的事務都能夠被通知到。
Zookeeper 的強一致性特性,能夠很好地保證在分佈式高併發情況下節點的創建一定能夠保證全局唯一性,即Zookeeper將會保證客戶端無法重複創建一個已經存在的數據節點。可以利用Zookeeper這個特性,實現排他鎖
(2) 共享鎖
共享鎖,又稱讀鎖。如果事務T1對數據對象O1加上了共享鎖,那麼當前事務只能對O1進行讀取操作,其他事務也只能對這個數據對象加共享鎖,直到該數據對象上的所有共享鎖都被釋放。
共享鎖與排他鎖的區別在於,加了排他鎖之後,數據對象只對當前事務可見,而加了共享鎖之後,數據對象對所有事務都可見
4.監聽與通知機制
Watcher機制官方解釋:一個Watch事件是一個一次性的觸發器,當被設置了Watch的數據發生了改變的時候,則服務器將這個改變發送給設置了Watch的客戶端,以便通知它們。
其實上面描述的很多功能都用到了watcher機制,比如master選舉的Znode監聽,當備用的master發現Znode已經被其它master創建後,這是備用master會註冊監聽這個Znode的變化。還有鎖機制,毋庸置疑,也是基於監聽機制上才能實現,比如釋放鎖的時候,需要告訴其它訪問的應用這個Znode代表的鎖已經釋放,通知其它應用可以獲取鎖以進行下一步操作。
Watcher機制的特點:
- 註冊的監聽是一次的,如果你還需要監聽第二次,那麼就要重新註冊。數據發生改變時,一個watcher event會被髮送到client,但是client只會收到一次這樣的信息
- watcher event異步發送 watcher 的通知事件從server發送到client是異步的
四、實際應用
1.hadoop
Hadoop HA:不論是namenode的HA或是yarn的HA,都通過zkfc機制來選舉控制只有一個active狀態的namenode和resourcemanager在工作,這裏通過上面描述的master的選舉功能,採用的是排他鎖完成的選舉,我們通過zk可視化客戶端可以清晰地看到上圖Znode的結構,yarn和hdfs的路徑下各有一把鎖的標記文件,同時文件的內容也帶有active節點的信息。
yarn application容錯:在yarn上跑任務時,所有application啓動時都會把application的信息寫入zk裏面,右邊是寫入的一堆序列化的二進制信息,假如這時active狀態的resourcemanager突然掛了,standby的轉換成active狀態後會接管掛之前的application,這樣resourcemanager主從的切換對正在yarn運行的application不影響。
yarn的容錯機制也帶來一系列問題:
- 一個spark任務在yarn運行突然失敗了,默認會重試,重試前會把上一次的driver端的信息寫入zk,這時如果driver端的數據太大,比如你代碼裏broadcast一個很大的文件,這樣很容易把zk寫滿寫掛,直接導致zk不能工作,zk停止工作直接的代價是整個集羣不能正常工作,簡直毀滅性的代價,有幸經歷過一次,後續會整理博客發出來
- 如上圖的application的信息存放在zk裏面是持久化的節點,不會自動刪除,當yarn的歷史任務運行太多的時候,zk裏面會存一堆的application的歷史信息,導致zk的內存佔用越來越大,也影響性能,需要寫腳本手工清除
2.hbase
zk在hbase應用的功能主要兩個:
- hbase regionserver 向zookeeper註冊,提供hbase regionserver狀態信息(是否在線),HMaster通過watcher監聽regionserver的存活情況,並不是HMaster跟Regionserver通過心跳機制檢測。
- HMaster啓動時候會將hbase系統表-ROOT- 加載到 zookeeper cluster,通過zookeeper cluster可以獲取當前系統表.META.的存儲所對應的regionserver信息
zookeeper是hbase集羣的"協調器"。由於zookeeper的輕量級特性,因此我們可以將多個hbase集羣共用一個zookeeper集羣,以節約大量的服務器。多個hbase集羣共用zookeeper集羣的方法是使用同一組ip,修改不同hbase集羣的"zookeeper.znode.parent"屬性,讓它們使用不同的根目錄。比如cluster1使用/hbase-c1,cluster2使用/hbase-c2,等等
3.kafka
zk在kafka的應用功能主要三個:
- broker的註冊:我們上面描述的zk一個基本的功能是做集羣的管理,所有broker會把自己的id號註冊進zk的Znode裏面,通過事件監聽的機制,各個broker之間都知道所有兄弟的存活情況。當有broker啓動時,會在zk裏面創建Znode,並通知其它broker;當有broker下線或掛掉時,同樣會通知其它broker。
- topic的註冊:當topic創建時,kafka會把topic的name、partition、leader、topic的分佈情況等信息寫入zk裏面,當broker退出時,會觸發zookeeper更新其對應topic分區的isr列表,並決定是否需要做消費者的負載均衡
- consumer的註冊:當有新的消費者消費kafka數據時,會在zk中創建Znode保存一些信息,節點路徑爲ls /consumers/{group_id},其節點下有三個子節點,分別爲[ids, owners, offsets]。在常用的offset的維護中,應用消費kafka數據時要設置參數enable.auto.commit爲true纔會自動更新offset
4.hive
zk在hive的應用主要兩個:
- hiveserver2的選舉:在上面的namenode選舉中用的是排他鎖,而hiveserver2用的鎖是共享鎖,如下圖
可以看到有兩個節點在zk中註冊,會創建有序的臨時節點,每個Znode都有一串數字後綴,zk默認是同一個鎖路徑下id最小的那個znode獲取鎖,也就是上圖中的後綴000000228所代表的hiveserver2節點是主的狀態,假如這個節點掛了,這個znode會自動刪除,watcher機制會通知另一個hiveserver2上位
- 表數據鎖:Hive 鎖機制是爲了讓 Hive 支持併發讀寫的原子性而設計的 特性,比如,一個sql在insert數據,這時需要鎖住表,不讓其它sql去讀取表數據,否則會有問題。等到釋放鎖之後,也就是insert完了其它客戶端才能讀取這個表,hive表的鎖機制非常有必要,通過
hive.support.concurrency=true
來開啓,後面打算專門一節來講hive的鎖機制,這裏就不詳述了。
五、總結
今天淺談了一下zookeeper的基礎知識點和在大數據的基本應用,其實zk的應用非常廣,還有比如統一的命名空間、做配置中心等等功能這裏沒講。總之zk是專門爲分佈式服務而生的,強大的協調能力爲大數據生態提供了強大的後勤保障。在後面的章節裏,我會把zk結合大數據的具體應用和才過的一些坑跟大家分享,讓大家知道zk的重要性,畢竟zk是大數據的心臟!