在Zookeeper的官網上有這么一句話:ZooKeeper is a centralized(統一的) service for maintaining(維護) configuration information, naming, providing distributed synchronization, and providing group services.
這主要描述Zookeeper可以干哪些事情:配置管理,名字服務,提供分布式同步以及集群管理。
那這些服務又到底是什么呢?
我們為什么需要這樣的服務?
我們又為什么要使用Zookeeper來實現呢,使用Zookeeper有什么優勢?
接下來會挨個介紹這些到底是什么,以及有哪些開源系統中使用了。
一、配置管理
在開發應用中除了代碼外,還有一些就是各種配置。比如數據庫連接等。一般我們都是使用配置文件的方式,在代碼中引入這些配置文件。當我們只有一種配置,只有一台服務器,並且不經常修改的時候,使用配置文件是一個很好的做法,但是如果我們配置非常多,有很多服務器都需要這個配置,而且還可能是動態的話使用配置文件就不是個好主意了。這個時候往往需要尋找一種集中管理配置的方法,我們在這個集中的地方修改了配置,所有對這個配置感興趣的應用程序都可以獲得變更。比如我們可以把配置放在數據庫里,然后所有需要配置的服務都去這個數據庫讀取配置。但是,因為很多服務的正常運行都非常依賴這個配置,所以需要這個集中提供配置服務的服務具備很高的可靠性。一般我們可以用一個集群來提供這個配置服務,但是用集群提升可靠性,那如何保證配置在集群中的一致性呢? 這個時候就需要使用一種實現了一致性協議的服務了。Zookeeper就是這種服務,它使用ZAB協議這種一致性協議來提供一致性。現在有很多開源項目使用Zookeeper來維護配置,比如在HBase中,客戶端就是連接一個Zookeeper,獲得必要的HBase集群的配置信息,然后才可以進一步操作。還有在開源的消息隊列Kafka中,也使用Zookeeper來維護broker(代理)的信息。在Alibaba開源的SOA框架Dubbo中也廣泛的使用Zookeeper管理一些配置來實現服務治理。
dubbo是分布式的遠程調用框架,推薦使用zk作為注冊和中心,利用zk來實現負載均衡,存儲服務的元數據,向外提供服務
二、名字服務
名字服務這個比較好理解。比如為了通過網絡訪問一個系統,我們得知道對方服務器的【ip:port】或域名,IP地址對人類記憶非常不友好,這個時候就需要使用方便記憶的域名來訪問目標系統。但計算機實際上是不能識別域名的。該怎么辦?如果互聯網中每台計算機里都備有一份域名到IP地址的映射關系(host文件),這個倒是能解決一部分問題,但是如果域名對應的IP發生變化了又該怎么辦呢?於是就有了DNS(Domain name resolution)這個東西。在通過網絡訪問一個目標系統時,只需要訪問一個大家熟知的(DNS)的服務器站點,它就會告訴你這個域名對應的IP是什么。同樣的在應用中也會存在這類問題,特別是在應用服務特別多的時候,如果在本地保存服務的地址的時候會非常不方便,但是如果只需要訪問一個大家都熟知的站點,並提供統一的入口,那么維護起來會方便很多。
三、分布式鎖
其實在第一篇文章中已經介紹了Zookeeper是一個分布式協調服務。我們可以利用Zookeeper來協調多個分布式進程之間的活動。比如在一個分布式環境中,為了提高可靠性,集群的每台服務器上都部署了同樣的服務。但是一件事情如果集群中的每個服務器都進行的話,那相互之間就要協調,編程起來將非常復雜。而如果我們只讓一個服務進行操作,那又存在單點。通常還有一種做法就是使用分布式鎖,在某個時刻只讓一個服務去干活,當這台服務出問題的時候鎖釋放,立即fail over(故障轉移)讓另外的服務去干活。這在很多分布式系統中都是這么做,這種設計有一個更好聽的名字叫Leader Election(leader選舉)。比如HBase的Master就是采用這種機制。但要注意的是分布式鎖跟同一個進程的鎖還是有區別的,所以使用的時候要比同一個進程里的鎖更謹慎的使用。
科普:分布式與集群的區別
其實分布式不一定就是不同的組件,同一個組件也可以,關鍵在於是否通過交換信息的方式進行協作
。比如說Zookeeper的節點都是對等的,但它自己就構成一個分布式系統。
也就是說,分布式是指通過網絡連接的多個組件,通過交換信息協作而形成的系統。而集群,是指同一種組件的多個實例,形成的邏輯上的整體。
這兩個概念並不完全沖突,分布式系統也可以是一個集群,例子就是前面說的zookeeper等,它的特征是服務之間會互相通信協作
,這是屬於是分布式系統,並且不是集群的情況,就是多個不同組件構成的系統;
是集群,並且不是分布式系統的情況:比如多個經過負載均衡的HTTP服務器,它們之間不會互相通信,如果不帶上負載均衡的部分的話,一般不叫做分布式系統。
假如A是服務那么N個A就是集群。將X服務拆分成A、B、C三種不同的服務,此時ABC三種服務構成一種分布式服務,分布式就將一個服務拆成多個服務,集群是多台相同服務一起提供服務
四、集群管理
在分布式系統的集群中,經常會由於各種原因,比如硬件故障,軟件故障,網絡問題,有些集群節點會進進出出。會有新的節點加入進來,也會有老的節點退出集群。這個時候,集群中其他機器就需要感知到這種變化,然后根據這種變化做出相應決策。比如在一個分布式存儲系統中,有一個中央控制節點負責存儲任務的分配,當有新的存儲工作進入系統的時候,這個中央控制節點就要根據集群的最新狀態來分配存儲節點。這個時候中央控制節點就需要具備動態感知集群目前的狀態的能力。不僅如此,比如一個分布式的SOA(微服務)架構中,某個服務是一個集群提供的,當消費者訪問這個服務的時候,就需要采用某種機制發現現在有哪些節點可以提供該服務(這就稱為服務發現,比如Alibaba開源的SOA框架Dubbo就采用了Zookeeper作為服務發現的底層機制)。還有開源的Kafka隊列就采用了Zookeeper作為Cosnumer的上下線管理。
自己的一些理解:
Leader可以接受client請求,也接收其他Server轉發的寫請求,負責更新系統狀態。 Follower也可以接收client請求,如果是寫請求將轉發給Leader來更新系統狀態,讀請求則由Follower的內存數據庫直接響應。
事物操作
在ZooKeeper中,能改變ZooKeeper服務器狀態的操作稱為事務操作。
一般包括數據節點創建與刪除、數據內容更新和客戶端會話創建與失效等操作。對應每一個事務請求,ZooKeeper都會為其分配一個全局唯一的事務ID,用 ZXID 表示,通常是一個64位的數字。每一個 ZXID對應一次更新操作,從這些 ZXID 中可以間接地識別出 ZooKeeper 處理這些事務操作請求的全局順序。
Watcher(事件監聽器)
是ZooKeeper中一個很重要的特性。ZooKeeper允許用戶在指定節點上注冊一些 Watcher,並且在一些特定事件觸發的時候,ZooKeeper服務端會將事件通知到感興趣的客戶端上去。這個機制是ZooKeeper實現分布式協調服務的重要特性。
ZooKeeper支持一種Watch(監聽)操作,Client可以在某個ZNode上設置一個Watcher,來Watch該ZNode上的變化。如果該ZNode上有相應的變化,就會觸發這個Watcher,把相應的事件通知給設置Watcher的Client。需要注意的是,ZooKeeper中的Watcher是一次性的,即觸發一次就會被取消,如果想繼續Watch的話,需要客戶端重新設置Watcher。
ZooKeeper數據模型的結構,整體上可以看作是一棵樹,每個節點稱做一個ZNode。每個ZNode都可以通過其路徑被唯一標識,並且每個ZNode上可存儲少量數據(默認是1M, 可以通過配置修改, 通常不建議在ZNode上存儲大量的數據)
節點狀態
每個集群中的節點都有一個狀態(LOOKING,LEADING, FOLLOWING, OBSERVING這4種中的一種),每個節點啟動的時都是LOOKING狀態,如果這個節點參與選舉但最后不是leader,則狀態是FOLLOWING,如果不參與選舉則是OBSERVING,leader的狀態是LEADING。
關於ZooKeeper集群服務器數
ZooKeeper官方確實給出了關於奇數的建議。
一個 ZooKeeper 集群如果要對外提供可用的服務,那么集群中必須要有過半的機器正常工作並且彼此之間能夠正常通信。基於這個特性,如果想搭建一個能夠允許N台機器down掉的集群,那么就要部署一個由 2*N+1 台服務器構成的 ZooKeeper 集群。因此,一個由 3 台機器構成的ZooKeeper 集群,能夠在掛掉 1 台機器后依然正常工作,而對於一個由 5 台服務器構成的ZooKeeper 集群,能夠對2台機器掛掉的情況進行容災。注意,如果是一個由6台服務器構成的 ZooKeeper 集群,同樣只能夠掛掉 2 台機器,因為如果掛掉 3 台,剩下的機器就無法實現過半了。
【集群中只要有超過半數的機器是正常的,那么整個集群對外就可用】
也就是說如果有2個zookeeper,那么只要有1個死了zookeeper就不能用了,因為1沒有過半,所以2個zookeeper的死亡容忍度為0;同理,要是有3個zookeeper,一個死了,還剩下2個正常的,過半了,所以3個zookeeper的容忍度為1;同理你多列舉幾個:2->0;3->1;4->1;5->2;6->2會發現一個規律,2n和2n-1的容忍度是一樣的,都是n-1,所以為了更加高效,何必增加那一個不必要的zookeeper呢,這就是為什么服務器集群的數量建議為奇數的原因。
ZooKeeper可伸縮性
ZooKeeper為什么要引入Observer這個角色呢?在ZooKeeper中引入Observer,主要是為了使ZooKeeper具有更好的可伸縮性。伸縮性在這里是說,如果系統的工作負載可以通過給系統分配更多的節點資源來降低整個系統的負載(想象一下集群),那么就稱這個系統是可伸縮的;一個不可伸縮的系統是無法通過增加節點資源來提升系統對外服務的性能,甚至會在工作負載增加時,性能會急劇下降。
Observer出現以前,ZooKeeper的伸縮性由Follower來實現,我們可以通過添加Follower節點的數量來保證ZooKeeper服務的讀性能。但隨着Follower節點數量的增加(參與投票節點數量的增加),ZooKeeper服務的寫性能受到了影響(寫操作需要經過投票機制)。
ZAB協議規定
Client的所有寫請求,都要轉發給ZK服務中唯一的Server—Leader,由Leader根據該請求發起一個Proposal(提案/請求)。然后,其他的Server對這個Proposal進行Vote(投票)。之后,Leader對Vote(投票)進行收集,當Vote數量過半時Leader會向所有的Server通知這個寫操提案通過的消息。最后,當Client所連接的Server收到該消息時,會把該操作更新到自己的內存中,並對Client的寫請求做出回應。
從圖中可以看出, ZooKeeper服務器在上述協議中實際扮演了兩個職能。它們一方面從客戶端接受連接與操作請求,另一方面對操作結果進行投票。這兩個職能在 ZooKeeper集群擴展的時候彼此制約。
例如,當希望增加ZK服務中Client數量(並發度)的時候,那么就需要增加Server的數量,來支持這么多的客戶端。但在觀察ZAB協議對寫請求的處理過程可以發現,增加服務器的數量,則增加了Leader在協議中投票過程的壓力,因為Leader節點必須等待集群中過半Server響應投票機制,於是節點的增加使得部分計算機運行較慢,從而拖慢整個投票過程的可能性也隨之提高,寫操作也會隨之下降。這正是在實際操作中看到的問題——隨着 ZooKeeper 集群變大,寫操作的吞吐量會下降。
所以,不得不在增加Client數量的願望與希望保持較好吞吐性能的願望之間進行權衡取舍。為了打破這個耦合的關系,就引入了不參與投票的服務器稱為Observer。 Observer可以接受客戶端的連接,並將寫請求轉發給Leader節點。但是Leader節點不會要求 Observer參加投票。相反,Observer不參與投票過程,僅僅在上述第3歩那樣,和其他服務節點一起得到投票結果,更新到自己的內存中,並對client的寫請求做出反應。
zookeeper的角色
1、領導者(leader) : 負責進行投票的發起和決議,更新系統狀態。
2、學習者(learner): 包括跟隨者(follower)和觀察者(observer)。
跟隨者(follower)用於接受客戶端請求並向客戶端返回結果,在選舉過程中參與投票。
觀察者(observer)可以接受客戶端連接,將寫請求轉發給leader,但是它不參與投票過程,只同步leader的狀態,它的目的是為了擴展系統,提高讀寫的速度。
3、客戶端(client):請求發起方。
Zookeeper工作流程-Leader
1 .恢復數據;
2 .維持與Learner的心跳,接收Learner請求並判斷Learner的請求消息類型;
3 .Learner的消息類型主要有PING消息、REQUEST消息、ACK消息、REVALIDATE消息,根據不同的消息類型,進行不同的處理。
PING 消息是指Learner的心跳信息;
REQUEST消息是Follower發送的提議信息,包括寫請求及同步請求;
ACK消息是 Follower的對提議的回復,超過半數的Follower通過,則commit該提議;
REVALIDATE(重新驗證)消息是用來延長SESSION有效時間。
Zookeeper工作流程-Follower
Follower主要有四個功能:
1.向Leader發送請求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);
2.接收Leader消息並進行處理;
3.接收Client的請求,如果為寫請求,發送給Leader進行投票;
4.更新自己的狀態,並返回Client結果。
Follower的消息循環處理如下幾種來自Leader的消息
1 .PING消息: 心跳消息;
2 .PROPOSAL消息:Leader發起的提案,要求Follower投票;
3 .COMMIT消息:服務器端最新一次提案的信息;
4 .UPTODATE消息:表明同步完成;
5 .REVALIDATE消息:根據Leader返回的REVALIDATE結果,關閉待revalidate的session還是允許其接受消息;
6 .SYNC消息:返回SYNC結果到客戶端,這個消息最初由客戶端發起,用來強制從Leader得到最新的更新。
Zookeeper節點數據操作流程
注:
1.在Client向Follwer發出一個寫的請求
2.Follwer把請求轉發給Leader處理
3.Leader接收到以后開始發起投票(提案)並通知Follwer進行投票
4.Follwer把各自投票結果ASK發送給Leader
5.Leader將結果匯總后如果投票通過過半則需要寫入,則開始寫入的同時把寫入操作通知給Follwer,然后commit;
6.Follwer根據Leader的通知決定是否更新自己的內存,並且把寫請求的結果返回給Client;
Zookeeper設計目的
1.最終一致性:client不論連接到哪個Server,展示給它都是同一個視圖,這是zookeeper最重要的性能。
2.可靠性:具有簡單、健壯、良好的性能,如果消息被某一台服務器接受,那么它將被所有的服務器接受。
3.實時性:Zookeeper保證客戶端將在一個時間間隔范圍內獲得服務器的更新信息,或者服務器失效的信息。但由於網絡延時等原因,Zookeeper不能保證兩個客戶端能同時得到剛更新的數據,如果需要最新數據,應該在讀數據之前調用sync()接口。
4.等待無關(wait-free):慢的或者失效的client不干預快速的client的請求,使得每個client都能進行有效的等待。
5.原子性:更新只能成功或者失敗,沒有中間狀態。
6.順序性:包括全局有序和偏序兩種:全局有序是指如果在一台服務器上消息a在消息b前發布,則在所有Server上消息a都將在消息b前被發布;偏序是指如果一個消息b在消息a后被同一個發送者發布,a必將排在b前面。