轉自:http://www.lxweimin.com/p/84ad63127cd1
作者:Jeffbond
簡介
ZooKeeper是一個開源的分布式協調服務,由雅虎創建,是Google Chubby的開源實現。ZooKeeper的設計目標是將那些復雜且容易出錯的分布式一致性服務封裝起來,構成一個高效可靠的原語集,并以一系列簡單易用的接口提供給用戶使用。
ZooKeeper是一個典型的分布式數據一致性的解決方案。分布式應用程序可以基于它實現諸如數據發布/訂閱、負載均衡、命名服務、分布式協調/通知、集群管理、Master選舉、分布式鎖和分布式隊列等功能。ZooKeeper可以保證如下分布式一致性特性。
順序一致性
從同一個客戶端發起的事務請求,最終將會嚴格按照其發起順序被應用到ZooKeeper中。原子性
所有事務請求的結果在集群中所有機器上的應用情況是一致的,也就是說要么整個集群所有集群都成功應用了某一個事務,要么都沒有應用,一定不會出現集群中部分機器應用了該事務,而另外一部分沒有應用的情況。單一視圖
無論客戶端連接的是哪個ZooKeeper服務器,其看到的服務端數據模型都是一致的。可靠性
一旦服務端成功地應用了一個事務,并完成對客戶端的響應,那么該事務所引起的服務端狀態變更將會被一直保留下來,除非有另一個事務又對其進行了變更。實時性
通常人們看到實時性的第一反應是,一旦一個事務被成功應用,那么客戶端能夠立即從服務端上讀取到這個事務變更后的最新數據狀態。這里需要注意的是,ZooKeeper僅僅保證在一定的時間段內,客戶端最終一定能夠從服務端上讀取到最新的數據狀態。
基本概念
本節將介紹ZooKeeper的幾個核心概念。這些概念貫穿于之后對ZooKeeper更深入的講解,因此有必要預先了解這些概念。
集群角色
在ZooKeeper中,有三種角色:
- Leader
- Follower
- Observer
一個ZooKeeper集群同一時刻只會有一個Leader,其他都是Follower或Observer。
ZooKeeper配置很簡單,每個節點的配置文件(zoo.cfg)都是一樣的,只有myid文件不一樣。myid的值必須是zoo.cfg中server.{數值}的{數值}部分。
zoo.cfg文件內容示例:
maxClientCnxns=0
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
dataDir=/var/lib/zookeeper/data
# the port at which the clients will connect
clientPort=2181
# the directory where the transaction logs are stored.
dataLogDir=/var/lib/zookeeper/logs
server.1=192.168.20.101:2888:3888
server.2=192.168.20.102:2888:3888
server.3=192.168.20.103:2888:3888
server.4=192.168.20.104:2888:3888
server.5=192.168.20.105:2888:3888
minSessionTimeout=4000
maxSessionTimeout=100000
在裝有ZooKeeper的機器的終端執行 zookeeper-server status
可以看當前節點的ZooKeeper是什么角色(Leader or Follower)。
[root@node-20-103 ~]# zookeeper-server status
JMX enabled by default
Using config: /etc/zookeeper/conf/zoo.cfg
Mode: follower
[root@node-20-104 ~]# zookeeper-server status
JMX enabled by default
Using config: /etc/zookeeper/conf/zoo.cfg
Mode: leader
如上,node-20-104是Leader,node-20-103是follower。
ZooKeeper默認只有Leader和Follower兩種角色,沒有Observer角色。
為了使用Observer模式,在任何想變成Observer的節點的配置文件中加入:peerType=observer
并在所有server的配置文件中,配置成observer模式的server的那行配置追加:observer,例如:
server.1:localhost:2888:3888:observer
ZooKeeper集群的所有機器通過一個Leader選舉過程來選定一臺被稱為『Leader』的機器,Leader服務器為客戶端提供讀和寫服務。
Follower和Observer都能提供讀服務,不能提供寫服務。兩者唯一的區別在于,Observer機器不參與Leader選舉過程,也不參與寫操作的『過半寫成功』策略,因此Observer可以在不影響寫性能的情況下提升集群的讀性能。
會話(Session)
Session是指客戶端會話,在講解客戶端會話之前,我們先來了解下客戶端連接。在ZooKeeper中,一個客戶端連接是指客戶端和ZooKeeper服務器之間的TCP長連接。ZooKeeper對外的服務端口默認是2181,客戶端啟動時,首先會與服務器建立一個TCP連接,從第一次連接建立開始,客戶端會話的生命周期也開始了,通過這個連接,客戶端能夠通過心跳檢測和服務器保持有效的會話,也能夠向ZooKeeper服務器發送請求并接受響應,同時還能通過該連接接收來自服務器的Watch事件通知。Session的SessionTimeout值用來設置一個客戶端會話的超時時間。當由于服務器壓力太大、網絡故障或是客戶端主動斷開連接等各種原因導致客戶端連接斷開時,只要在SessionTimeout規定的時間內能夠重新連接上集群中任意一臺服務器,那么之前創建的會話仍然有效。
數據節點(ZNode)
在談到分布式的時候,一般『節點』指的是組成集群的每一臺機器。而ZooKeeper中的數據節點是指數據模型中的數據單元,稱為ZNode。ZooKeeper將所有數據存儲在內存中,數據模型是一棵樹(ZNode Tree),由斜杠(/)進行分割的路徑,就是一個ZNode,如/hbase/master,其中hbase和master都是ZNode。每個ZNode上都會保存自己的數據內容,同時會保存一系列屬性信息。
注:
這里的ZNode可以理解成既是Unix里的文件,又是Unix里的目錄。因為每個ZNode不僅本身可以寫數據(相當于Unix里的文件),還可以有下一級文件或目錄(相當于Unix里的目錄)。
在ZooKeeper中,ZNode可以分為持久節點和臨時節點兩類。
持久節點
所謂持久節點是指一旦這個ZNode被創建了,除非主動進行ZNode的移除操作,否則這個ZNode將一直保存在ZooKeeper上。
臨時節點
臨時節點的生命周期跟客戶端會話綁定,一旦客戶端會話失效,那么這個客戶端創建的所有臨時節點都會被移除。
另外,ZooKeeper還允許用戶為每個節點添加一個特殊的屬性:SEQUENTIAL。一旦節點被標記上這個屬性,那么在這個節點被創建的時候,ZooKeeper就會自動在其節點后面追加上一個整型數字,這個整型數字是一個由父節點維護的自增數字。
版本
ZooKeeper的每個ZNode上都會存儲數據,對應于每個ZNode,ZooKeeper都會為其維護一個叫作Stat的數據結構,Stat中記錄了這個ZNode的三個數據版本,分別是version(當前ZNode的版本)、cversion(當前ZNode子節點的版本)和aversion(當前ZNode的ACL版本)。
狀態信息
每個ZNode除了存儲數據內容之外,還存儲了ZNode本身的一些狀態信息。用 get 命令可以同時獲得某個ZNode的內容和狀態信息。如下:
[zk: localhost:2181(CONNECTED) 23] get /yarn-leader-election/appcluster-yarn/ActiveBreadCrumb
appcluster-yarnrm1
cZxid = 0x1b00133dc0 //Created ZXID,表示該ZNode被創建時的事務ID
ctime = Tue Jan 03 15:44:42 CST 2017 //Created Time,表示該ZNode被創建的時間
mZxid = 0x1d00000063 //Modified ZXID,表示該ZNode最后一次被更新時的事務ID
mtime = Fri Jan 06 08:44:25 CST 2017 //Modified Time,表示該節點最后一次被更新的時間
pZxid = 0x1b00133dc0 //表示該節點的子節點列表最后一次被修改時的事務ID。注意,只有子節點列表變更了才會變更pZxid,子節點內容變更不會影響pZxid。
cversion = 0 //子節點的版本號
dataVersion = 11 //數據節點的版本號
aclVersion = 0 //ACL版本號
ephemeralOwner = 0x0 //創建該節點的會話的seddionID。如果該節點是持久節點,那么這個屬性值為0。
dataLength = 22 //數據內容的長度
numChildren = 0 //子節點的個數
在ZooKeeper中,version屬性是用來實現樂觀鎖機制中的『寫入校驗』的(保證分布式數據原子性操作)。
事務操作
在ZooKeeper中,能改變ZooKeeper服務器狀態的操作稱為事務操作。一般包括數據節點創建與刪除、數據內容更新和客戶端會話創建與失效等操作。對應每一個事務請求,ZooKeeper都會為其分配一個全局唯一的事務ID,用ZXID表示,通常是一個64位的數字。每一個ZXID對應一次更新操作,從這些ZXID中可以間接地識別出ZooKeeper處理這些事務操作請求的全局順序。
Watcher
Watcher(事件監聽器),是ZooKeeper中一個很重要的特性。ZooKeeper允許用戶在指定節點上注冊一些Watcher,并且在一些特定事件觸發的時候,ZooKeeper服務端會將事件通知到感興趣的客戶端上去。該機制是ZooKeeper實現分布式協調服務的重要特性。
ACL
ZooKeeper采用ACL(Access Control Lists)策略來進行權限控制。ZooKeeper定義了如下5種權限。
- CREATE: 創建子節點的權限。
- READ: 獲取節點數據和子節點列表的權限。
- WRITE:更新節點數據的權限。
- DELETE: 刪除子節點的權限。
- ADMIN: 設置節點ACL的權限。
注意:CREATE 和 DELETE 都是針對子節點的權限控制。
ZAB協議
ZAB協議概覽
ZooKeeper是Chubby的開源實現,而Chubby是Paxos的工程實現,所以很多人以為ZooKeeper也是Paxos算法的工程實現。事實上,ZooKeeper并沒有完全采用Paxos算法,而是使用了一種稱為ZooKeeper Atomic Broadcast(ZAB,ZooKeeper原子廣播協議)的協議作為其數據一致性的核心算法。
ZAB協議并不像Paxos算法和Raft協議一樣,是通用的分布式一致性算法,它是一種特別為ZooKeeper設計的崩潰可恢復的原子廣播算法。
接下來對ZAB協議做一個淺顯的介紹,目的是讓大家對ZAB協議有個直觀的了解。讀者不用太糾結于細節。至于更深入的細節,以后再專門分享。
基于ZAB協議,ZooKeeper實現了一種主備模式(Leader、Follower)的系統架構來保持集群中各副本之間數據的一致性。
具體的,ZooKeeper使用了一個單一的主進程(Leader)來接收并處理客戶端的所有事務請求,并采用ZAB的原子廣播協議,將服務器數據的狀態變更以事務Proposal的形式廣播到所有的副本進程上去(Follower)。ZAB協議的這個主備模型架構保證了同一時刻集群中只能有一個主進程來廣播服務器的狀態變更,因此能夠很好地處理客戶端大量的并發請求。另一方面,考慮到分布式環境中,順序執行的一些狀態變更其前后會存在一定的依賴關系,有些狀態變更必須依賴于比它早生成的那些狀態變更,例如變更C需要依賴變更A和變更B。這樣的依賴關系也對ZAB協議提出了一個要求:ZAB協議必須能夠保證一個全局的變更序列被順序應用。也就是說,ZAB協議需要保證如果一個狀態變更已經被處理了,那么所有依賴的狀態變更都應該已經被提前處理掉了。最后,考慮到主進程在任何時候都有可能出現崩潰退出或重啟現象,因此,ZAB協議還需要做到在當前主進程出現上述異常情況的時候,依然能夠正常工作。
ZAB協議的核心是定義了對應那些會改變ZooKeeper服務器數據狀態的事務請求的處理方式,即:
所有事務請求必須由一個全局唯一的服務器來協調處理,這樣的服務器被稱為Leader服務器,而剩下的其他服務器則成為Follower服務器。Leader服務器負責將一個客戶端事務請求轉換成一個事務Proposal(提案)并將該Proposal分發給集群中所有的Follower服務器。之后Leader服務器需要等待所有Follower服務器的反饋,一旦超過半數的Follower服務器進行了正確的反饋后,Leader就會再次向所有的Follower服務器分發Commit消息,要求對剛才的Proposal進行提交。
ZAB協議介紹
從上面的介紹中,我們已經了解了ZAB協議的核心,接下來更加詳細地講解下ZAB協議的具體內容。
ZAB協議包括兩種基本的模式,分別是崩潰恢復和消息廣播。在整個ZooKeeper集群啟動過程中,或是當Leader服務器出現網絡中斷、崩潰退出與重啟等異常情況時,ZAB協議就會進入恢復模式并選舉產生新的Leader服務器。當選舉產生了新的Leader服務器,同時集群中有過半的機器與該Leader服務器完成了狀態同步之后,ZAB協議就會退出恢復模式。其中,狀態同步是指數據同步,用來保證集群中存在過半的機器能夠和Leader服務器的數據狀態保持一致。
崩潰恢復模式包括兩個階段:Leader選舉和數據同步。
當集群中有過半的Follower服務器完成了和Leader服務器的狀態同步,那么整個集群就可以進入消息廣播模式了。
ZooKeeper典型應用場景
ZooKeeper是一個高可用的分布式數據管理與協調框架。基于對ZAB算法的實現,該框架能夠很好地保證分布式環境中數據的一致性。也是基于這樣的特性,使得ZooKeeper成為了解決分布式一致性問題的利器。
數據發布與訂閱(配置中心)
數據發布與訂閱,即所謂的配置中心,顧名思義就是發布者將數據發布到ZooKeeper節點上,供訂閱者進行數據訂閱,進而達到動態獲取數據的目的,實現配置信息的集中式管理和動態更新。
在我們平常的應用系統開發中,經常會碰到這樣的需求:系統中需要使用一些通用的配置信息,例如機器列表信息、數據庫配置信息等。這些全局配置信息通常具備以下3個特性。
- 數據量通常比較小。
- 數據內容在運行時動態變化。
- 集群中各機器共享,配置一致。
對于這樣的全局配置信息就可以發布到ZooKeeper上,讓客戶端(集群的機器)去訂閱該消息。
發布/訂閱系統一般有兩種設計模式,分別是推(Push)和拉(Pull)模式。
- 推:服務端主動將數據更新發送給所有訂閱的客戶端。
- 拉:客戶端主動發起請求來獲取最新數據,通常客戶端都采用定時輪詢拉取的方式。
ZooKeeper采用的是推拉相結合的方式。如下:
客戶端想服務端注冊自己需要關注的節點,一旦該節點的數據發生變更,那么服務端就會向相應的客戶端發送Watcher事件通知,客戶端接收到這個消息通知后,需要主動到服務端獲取最新的數據(推拉結合)。
命名服務(Naming Service)
命名服務也是分布式系統中比較常見的一類場景。在分布式系統中,通過使用命名服務,客戶端應用能夠根據指定名字來獲取資源或服務的地址,提供者等信息。被命名的實體通常可以是集群中的機器,提供的服務,遠程對象等等——這些我們都可以統稱他們為名字(Name)。其中較為常見的就是一些分布式服務框架(如RPC、RMI)中的服務地址列表。通過在ZooKeepr里創建順序節點,能夠很容易創建一個全局唯一的路徑,這個路徑就可以作為一個名字。
ZooKeeper的命名服務即生成全局唯一的ID。
分布式協調/通知
ZooKeeper中特有Watcher注冊與異步通知機制,能夠很好的實現分布式環境下不同機器,甚至不同系統之間的通知與協調,從而實現對數據變更的實時處理。使用方法通常是不同的客戶端都對ZK上同一個ZNode進行注冊,監聽ZNode的變化(包括ZNode本身內容及子節點的),如果ZNode發生了變化,那么所有訂閱的客戶端都能夠接收到相應的Watcher通知,并做出相應的處理。
ZK的分布式協調/通知,是一種通用的分布式系統機器間的通信方式。
心跳檢測
機器間的心跳檢測機制是指在分布式環境中,不同機器(或進程)之間需要檢測到彼此是否在正常運行,例如A機器需要知道B機器是否正常運行。在傳統的開發中,我們通常是通過主機直接是否可以相互PING通來判斷,更復雜一點的話,則會通過在機器之間建立長連接,通過TCP連接固有的心跳檢測機制來實現上層機器的心跳檢測,這些都是非常常見的心跳檢測方法。
下面來看看如何使用ZK來實現分布式機器(進程)間的心跳檢測。
基于ZK的臨時節點的特性,可以讓不同的進程都在ZK的一個指定節點下創建臨時子節點,不同的進程直接可以根據這個臨時子節點來判斷對應的進程是否存活。通過這種方式,檢測和被檢測系統直接并不需要直接相關聯,而是通過ZK上的某個節點進行關聯,大大減少了系統耦合。
工作進度匯報
在一個常見的任務分發系統中,通常任務被分發到不同的機器上執行后,需要實時地將自己的任務執行進度匯報給分發系統。這個時候就可以通過ZK來實現。在ZK上選擇一個節點,每個任務客戶端都在這個節點下面創建臨時子節點,這樣便可以實現兩個功能:
- 通過判斷臨時節點是否存在來確定任務機器是否存活。
- 各個任務機器會實時地將自己的任務執行進度寫到這個臨時節點上去,以便中心系統能夠實時地獲取到任務的執行進度。
Master選舉
Master選舉可以說是ZooKeeper最典型的應用場景了。比如HDFS中Active NameNode的選舉、YARN中Active ResourceManager的選舉和HBase中Active HMaster的選舉等。
針對Master選舉的需求,通常情況下,我們可以選擇常見的關系型數據庫中的主鍵特性來實現:希望成為Master的機器都向數據庫中插入一條相同主鍵ID的記錄,數據庫會幫我們進行主鍵沖突檢查,也就是說,只有一臺機器能插入成功——那么,我們就認為向數據庫中成功插入數據的客戶端機器成為Master。
依靠關系型數據庫的主鍵特性確實能夠很好地保證在集群中選舉出唯一的一個Master。但是,如果當前選舉出的Master掛了,那么該如何處理?誰來告訴我Master掛了呢?顯然,關系型數據庫無法通知我們這個事件。但是,ZooKeeper可以做到!
利用ZooKeepr的強一致性,能夠很好地保證在分布式高并發情況下節點的創建一定能夠保證全局唯一性,即ZooKeeper將會保證客戶端無法創建一個已經存在的ZNode。也就是說,如果同時有多個客戶端請求創建同一個臨時節點,那么最終一定只有一個客戶端請求能夠創建成功。利用這個特性,就能很容易地在分布式環境中進行Master選舉了。
成功創建該節點的客戶端所在的機器就成為了Master。同時,其他沒有成功創建該節點的客戶端,都會在該節點上注冊一個子節點變更的Watcher,用于監控當前Master機器是否存活,一旦發現當前的Master掛了,那么其他客戶端將會重新進行Master選舉。
這樣就實現了Master的動態選舉。
分布式鎖
分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。
分布式鎖又分為排他鎖和共享鎖兩種。
排他鎖
排他鎖(Exclusive Locks,簡稱X鎖),又稱為寫鎖或獨占鎖。
如果事務T1對數據對象O1加上了排他鎖,那么在整個加鎖期間,只允許事務T1對O1進行讀取和更新操作,其他任何事務都不能在對這個數據對象進行任何類型的操作(不能再對該對象加鎖),直到T1釋放了排他鎖。
可以看出,排他鎖的核心是如何保證當前只有一個事務獲得鎖,并且鎖被釋放后,所有正在等待獲取鎖的事務都能夠被通知到。
如何利用ZooKeeper實現排他鎖?
定義鎖
ZooKeeper上的一個ZNode可以表示一個鎖。例如/exclusive_lock/lock節點就可以被定義為一個鎖。
獲得鎖
如上所說,把ZooKeeper上的一個ZNode看作是一個鎖,獲得鎖就通過創建ZNode的方式來實現。所有客戶端都去/exclusive_lock節點下創建臨時子節點/exclusive_lock/lock。ZooKeeper會保證在所有客戶端中,最終只有一個客戶端能夠創建成功,那么就可以認為該客戶端獲得了鎖。同時,所有沒有獲取到鎖的客戶端就需要到/exclusive_lock節點上注冊一個子節點變更的Watcher監聽,以便實時監聽到lock節點的變更情況。
釋放鎖
因為/exclusive_lock/lock是一個臨時節點,因此在以下兩種情況下,都有可能釋放鎖。
- 當前獲得鎖的客戶端機器發生宕機或重啟,那么該臨時節點就會被刪除,釋放鎖。
- 正常執行完業務邏輯后,客戶端就會主動將自己創建的臨時節點刪除,釋放鎖。
無論在什么情況下移除了lock節點,ZooKeeper都會通知所有在/exclusive_lock節點上注冊了節點變更Watcher監聽的客戶端。這些客戶端在接收到通知后,再次重新發起分布式鎖獲取,即重復『獲取鎖』過程。
共享鎖
共享鎖(Shared Locks,簡稱S鎖),又稱為讀鎖。如果事務T1對數據對象O1加上了共享鎖,那么T1只能對O1進行讀操作,其他事務也能同時對O1加共享鎖(不能是排他鎖),直到O1上的所有共享鎖都釋放后O1才能被加排他鎖。
總結:可以多個事務同時獲得一個對象的共享鎖(同時讀),有共享鎖就不能再加排他鎖(因為排他鎖是寫鎖)
ZooKeeper在大型分布式系統中的應用
前面已經介紹了ZooKeeper的典型應用場景。本節將以常見的大數據產品Hadoop和HBase為例來介紹ZooKeeper在其中的應用,幫助大家更好地理解ZooKeeper的分布式應用場景。
ZooKeeper在Hadoop中的應用
在Hadoop中,ZooKeeper主要用于實現HA(Hive Availability),包括HDFS的NamaNode和YARN的ResourceManager的HA。同時,在YARN中,ZooKeepr還用來存儲應用的運行狀態。HDFS的NamaNode和YARN的ResourceManager利用ZooKeepr實現HA的原理是一樣的,所以本節以YARN為例來介紹。
從上圖可以看出,YARN主要由ResourceManager(RM)、NodeManager(NM)、ApplicationMaster(AM)和Container四部分組成。其中最核心的就是ResourceManager。
ResourceManager負責集群中所有資源的統一管理和分配,同時接收來自各個節點(NodeManager)的資源匯報信息,并把這些信息按照一定的策略分配給各個應用程序(Application Manager),其內部維護了各個應用程序的ApplicationMaster信息、NodeManager信息以及資源使用信息等。
為了實現HA,必須有多個ResourceManager并存(一般就兩個),并且只有一個ResourceManager處于Active狀態,其他的則處于Standby狀態,當Active節點無法正常工作(如機器宕機或重啟)時,處于Standby的就會通過競爭選舉產生新的Active節點。
主備切換
下面我們就來看看YARN是如何實現多個ResourceManager之間的主備切換的。
- 創建鎖節點
在ZooKeeper上會有一個/yarn-leader-election/appcluster-yarn
的鎖節點,所有的ResourceManager在啟動的時候,都會去競爭寫一個Lock子節點:/yarn-leader-election/appcluster-yarn/ActiveBreadCrumb
,該節點是臨時節點。ZooKeepr能夠為我們保證最終只有一個ResourceManager能夠創建成功。創建成功的那個ResourceManager就切換為Active狀態,沒有成功的那些ResourceManager則切換為Standby狀態。
[zk: localhost:2181(CONNECTED) 16] get /yarn-leader-election/appcluster-yarn/ActiveBreadCrumb
appcluster-yarnrm2
cZxid = 0x1b00133dc0
ctime = Tue Jan 03 15:44:42 CST 2017
mZxid = 0x1f00000540
mtime = Sat Jan 07 00:50:20 CST 2017
pZxid = 0x1b00133dc0
cversion = 0
dataVersion = 28
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 22
numChildren = 0
可以看到此時集群中ResourceManager2為Active。
注冊Watcher監聽
所有Standby狀態的ResourceManager都會向/yarn-leader-election/appcluster-yarn/ActiveBreadCrumb
節點注冊一個節點變更的Watcher監聽,利用臨時節點的特性,能夠快速感知到Active狀態的ResourceManager的運行情況。主備切換
當Active狀態的ResourceManager出現諸如宕機或重啟的異常情況時,其在ZooKeeper上連接的客戶端會話就會失效,因此/yarn-leader-election/appcluster-yarn/ActiveBreadCrumb
節點就會被刪除。此時其余各個Standby狀態的ResourceManager就都會接收到來自ZooKeeper服務端的Watcher事件通知,然后會重復進行步驟1的操作。
以上就是利用ZooKeeper來實現ResourceManager的主備切換的過程,實現了ResourceManager的HA。
HDFS中NameNode的HA的實現原理跟YARN中ResourceManager的HA的實現原理相同。其鎖節點為/hadoop-ha/mycluster/ActiveBreadCrumb
。
ResourceManager狀態存儲
在 ResourceManager 中,RMStateStore 能夠存儲一些 RM 的內部狀態信息,包括 Application 以及它們的 Attempts 信息、Delegation Token 及 Version Information 等。需要注意的是,RMStateStore 中的絕大多數狀態信息都是不需要持久化存儲的,因為很容易從上下文信息中將其重構出來,如資源的使用情況。在存儲的設計方案中,提供了三種可能的實現,分別如下。
- 基于內存實現,一般是用于日常開發測試。
- 基于文件系統的實現,如HDFS。
- 基于ZooKeeper實現。
由于這些狀態信息的數據量都不是很大,因此Hadoop官方建議基于ZooKeeper來實現狀態信息的存儲。在ZooKeepr上,ResourceManager 的狀態信息都被存儲在/rmstore
這個根節點下面。
[zk: localhost:2181(CONNECTED) 28] ls /rmstore/ZKRMStateRoot
[RMAppRoot, AMRMTokenSecretManagerRoot, EpochNode, RMDTSecretManagerRoot, RMVersionNode]
RMAppRoot 節點下存儲的是與各個 Application 相關的信息,RMDTSecretManagerRoot 存儲的是與安全相關的 Token 等信息。每個 Active 狀態的 ResourceManager 在初始化階段都會從 ZooKeeper 上讀取到這些狀態信息,并根據這些狀態信息繼續進行相應的處理。
小結:
ZooKeepr在Hadoop中的應用主要有:
- HDFS中NameNode的HA和YARN中ResourceManager的HA。
- 存儲RMStateStore狀態信息
ZooKeeper在HBase中的應用
HBase主要用ZooKeeper來實現HMaster選舉與主備切換、系統容錯、RootRegion管理、Region狀態管理和分布式SplitWAL任務管理等。
HMaster選舉與主備切換
HMaster選舉與主備切換的原理和HDFS中NameNode及YARN中ResourceManager的HA原理相同。
系統容錯
當HBase啟動時,每個RegionServer都會到ZooKeeper的/hbase/rs
節點下創建一個信息節點(下文中,我們稱該節點為"rs狀態節點"),例如/hbase/rs/[Hostname]
,同時,HMaster會對這個節點注冊監聽。當某個 RegionServer 掛掉的時候,ZooKeeper會因為在一段時間內無法接受其心跳(即 Session 失效),而刪除掉該 RegionServer 服務器對應的 rs 狀態節點。與此同時,HMaster 則會接收到 ZooKeeper 的 NodeDelete 通知,從而感知到某個節點斷開,并立即開始容錯工作。
HBase為什么不直接讓HMaster來負責RegionServer的監控呢?如果HMaster直接通過心跳機制等來管理RegionServer的狀態,隨著集群越來越大,HMaster的管理負擔會越來越重,另外它自身也有掛掉的可能,因此數據還需要持久化。在這種情況下,ZooKeeper就成了理想的選擇。
RootRegion管理
對應HBase集群來說,數據存儲的位置信息是記錄在元數據region,也就是RootRegion上的。每次客戶端發起新的請求,需要知道數據的位置,就會去查詢RootRegion,而RootRegion自身位置則是記錄在ZooKeeper上的(默認情況下,是記錄在ZooKeeper的/hbase/meta-region-server
節點中)。當RootRegion發生變化,比如Region的手工移動、重新負載均衡或RootRegion所在服務器發生了故障等是,就能夠通過ZooKeeper來感知到這一變化并做出一系列相應的容災措施,從而保證客戶端總是能夠拿到正確的RootRegion信息。
Region管理
HBase里的Region會經常發生變更,這些變更的原因來自于系統故障、負載均衡、配置修改、Region分裂與合并等。一旦Region發生移動,它就會經歷下線(offline)和重新上線(online)的過程。
在下線期間數據是不能被訪問的,并且Region的這個狀態變化必須讓全局知曉,否則可能會出現事務性的異常。對于大的HBase集群來說,Region的數量可能會多達十萬級別,甚至更多,這樣規模的Region狀態管理交給ZooKeeper來做也是一個很好的選擇。
分布式SplitWAL任務管理
當某臺RegionServer服務器掛掉時,由于總有一部分新寫入的數據還沒有持久化到HFile中,因此在遷移該RegionServer的服務時,一個重要的工作就是從WAL中恢復這部分還在內存中的數據,而這部分工作最關鍵的一步就是SplitWAL,即HMaster需要遍歷該RegionServer服務器的WAL,并按Region切分成小塊移動到新的地址下,并進行日志的回放(replay)。
由于單個RegionServer的日志量相對龐大(可能有上千個Region,上GB的日志),而用戶又往往希望系統能夠快速完成日志的恢復工作。因此一個可行的方案是將這個處理WAL的任務分給多臺RegionServer服務器來共同處理,而這就又需要一個持久化組件來輔助HMaster完成任務的分配。當前的做法是,HMaster會在ZooKeeper上創建一個SplitWAL節點(默認情況下,是/hbase/SplitWAL
節點),將"哪個RegionServer處理哪個Region"這樣的信息以列表的形式存放到該節點上,然后由各個RegionServer服務器自行到該節點上去領取任務并在任務執行成功或失敗后再更新該節點的信息,以通知HMaster繼續進行后面的步驟。ZooKeeper在這里擔負起了分布式集群中相互通知和信息持久化的角色。
小結:
以上就是一些HBase中依賴ZooKeeper完成分布式協調功能的典型場景。但事實上,HBase對ZooKeepr的依賴還不止這些,比如HMaster還依賴ZooKeeper來完成Table的enable/disable狀態記錄,以及HBase中幾乎所有的元數據存儲都是放在ZooKeeper上的。
由于ZooKeeper出色的分布式協調能力及良好的通知機制,HBase在各版本的演進過程中越來越多地增加了ZooKeeper的應用場景,從趨勢上來看兩者的交集越來越多。HBase中所有對ZooKeeper的操作都封裝在了org.apache.hadoop.hbase.zookeeper這個包中,感興趣的同學可以自行研究。