首先咱們先明確下,在ZooKeeper服務器中的幾個基本概念。算法
請求:請求表示源自客戶端發起的操做,事務一般包含節點中新的數據字段值和該節點新的版本號
事務:包含了對應請求處理而改變ZooKeeper狀態所需執行的步驟
標識符:當羣首收到了一個客戶端改變ZooKeeper狀態的請求,便會產生一個事務,就會爲該事務分配一個標識符,即ZooKeeper會話ID(zxid),經過Zxid對事務進行標識,就能夠按照羣首所指定的順序在各個服務器中按序執行.apache
zxid是一個long型(64)位整數,分爲兩部分:時間戳(epoch)部分和計數器(counter)部分.
羣首爲集羣中的服務器選擇出來的一個服務器,並會被集羣所承認.設置羣首的目的是爲了對客戶端所發起的ZooKeeper狀態變動請求進行排序,包括:create
、setData
、和delete
操做. 羣首將每個請求轉換爲一個事務,將這些事務發送給追隨者,確保集羣按照羣首肯定的順序接受並處理事務.
這裏爲了瞭解管理權的原理,一個服務器必須被仲裁的法定數量的服務器所承認. 法定數量必須集羣數量是指可以交錯在一塊兒,以免咱們常說的腦裂問題:即兩個集合的服務器分別獨立運行,造成了兩個集羣,這種狀況將致使整個系統狀態不一致.
服務器
接下來咱們仔細探究下羣首選舉的過程:網絡
每一個服務器在啓動後進行LOOKING狀態,開始選舉一個新的羣首或查找已經存在的羣首,若是羣首已經存在,其餘服務器就會通知這個新啓動的服務器,告知哪一個服務器是羣首,與此同時,新的服務器會與羣首創建鏈接,確保本身的狀態與羣首一致.性能
若是集羣中全部的服務器均處LOOKING狀態,這些服務器之間就會進行通訊來選舉一個羣首,經過信息交換對羣首選舉達成共識的選擇.在本次選舉中勝出的服務器將進入LEADING狀態,而集羣中其餘服務器將進入FOLLOWING狀態.學習
對於羣首選舉的通知協議其實很是簡單:spa
當一個服務器進入LOOKING狀態就會向集羣中每一個服務器發送一個通知消息,該消息中包括該服務器的投票信息,投票中包含 服務器標識符(sid) 和 最近執行的事務zxis信息 ,好比,一個服務器所發送的投票信息爲(1,5) ,表示該服務器的sid爲1, 最近執行的事務的zxid爲5,(由於羣首選舉的目的,zxid只有一個數字,而在其餘協議中,zxid則由時間戳和計數器組成).
當一個服務器收到一個投票信息,該服務器將會根據如下規則修改本身的投票信息:線程
簡而言之,只有最新的服務器將贏得選舉,由於其擁有最近一次的zxid.日誌
在ZooKeeper中對應的實現選舉的Java類爲QuorumPeer,其中的run方法實現了服務器的主要工做循環.當進入LOOKING狀態,將會執行looForLeader方法來進行羣首的選舉,該方法主要執行咱們上面所討論的協議,該方法返回前,在該方法中會將服務器狀態設置爲LEADING 或 FOLLOWING狀態,固然還可能爲OBSERVING狀態.
下面咱們經過一副來重溫這個協議的執行過程:code
並非全部執行過程都如上圖所示的那樣,在下圖中咱們,展現了另外一種狀況的例子.
服務器s2作出了錯誤的判斷,選舉了另外一個服務器s1而不是服務器s3,雖然s3的zxid值更高,但在從服務器s3向s2傳送消息時發生了網絡故障致使長時間延遲,與此同時,服務器s2選擇了服務器s1做爲羣首,最終,服務器s3和服務器S1組成了仲裁數量(quorum),並將忽略服務器s2.
從這裏能夠看出,若是服務器s2在進行羣首選舉時多等待一會,它就能作出正確的判斷.
若是想實現一個新的羣首選舉算法,咱們須要實現一個quorum
包中的Election接口.
ZooKeeper包中已經有了讓咱們本身選擇羣首選舉實現的類,具體能夠查看org.apache.zookeeper.server.quorum.QuorumPeer#createElectionAlgorithm
方法;
Zab: ZooKeeper原子廣播協議(ZooKeeper Atomic Broadcast protocol),用於服務器提交確認一個更新事務的提交.
下面舉個例子,假設咱們如今有一個活動的羣首服務器,並擁有仲裁數量的追隨者支持該羣首的管理權,經過該協議提交一個事務,相似於一個兩階段提交:
下圖說明了這一過程的具體步驟順序,咱們假設羣首經過隱式方式給本身發送消息.
在應答消息以前,追隨者還須要執行一些檢查操做.追隨者將會檢查所發送的提案消息是否屬於其所追隨的羣首,並確認羣首所廣播的提案消息和提交事務消失的順序正確;
Zab保障瞭如下幾個重要屬性:
第一個屬性保證事務在服務器之間的傳送順序一致,而第二個豎向地保證服務器不會跳過任何事務.假設事務爲狀態變動操做,每一個狀態變動操做又依賴前一個狀態變動操做的結果,若是跳過事務就會致使結果不一致性,而兩階段提交保證了事務的順序.
大部分Zab的代碼存在於Leader、LearnerHandler和Follower。Leader和LearderHandler的實例由羣首服務器執行,而Follower的實例由追隨者執行。Leader.lead
和Follower.followLeader
是兩個重要方法,他們在服務器在QuorumPeer
中從 LOOKING
轉換到LEADING
或者FOLLOWING
時獲得調用.
觀察者和追隨者之間有一些共同點,具體來講,它們提交來自羣首的提議. 不一樣於追隨者的是,觀察者不參與選舉過程,它們僅僅學習經由INFORM消息提交的提議. 因爲羣首將狀態變化發送給追隨者和觀察者,這兩種服務器也都被稱爲學習者.
羣首,追隨者基本上都是服務器.咱們在實現服務器時使用的主要抽象概念是請求處理器. 請求處理器是對處理流水線上不一樣階段的抽象. 每個服務器實現了一個請求處理器的序列.咱們能夠把一個處理器想象成添加到請求處理的一個元素. 一條請求通過服務器流水線上全部處理器的處理後被稱爲獲得徹底處理.注意: 請求處理器
ZooKeeper代碼裏有一個交RequestProcessor
的接口.這個接口的主要方法是processRequest,它接受一個Request的參數.在一條請求處理器的流水線上,對相鄰處理器的請求的處理一般經過隊列實現解耦合.當一個處理器有一條請求須要下一個處理器進行處理時,它將這條請求加入隊列.而後,它將處於等待狀態直到下一個處理器處理完此消息;
ZooKeeper中最簡單的流水線式獨立服務器(ZooKeeperServer類
).下圖描述了此類服務器的流水線:
PrepRequestProcessor
接口客戶端的請求並執行這個請求,處理結果則是生成一個事務. 事務是執行一個操做的結果, 該操做會反映到ZooKeeper的數據樹上. 事務信息將會以頭部記錄和事務記錄的方式添加到Request對象中. 同時還要注意,只有改變ZooKeeper狀態的操做纔會產生事務,對於讀操做並不會產生任何事務. 所以,對於讀請求的Request對象中,事務成員屬性的引用值爲null.
下一個請求處理器爲SynRequestProcessor
. 它負責將事務持久化到磁盤上. 實際上就是將事務數據按照順序追加到事務日誌中, 並生成快照數據.
在下一個處理器也是最後一個爲FinalRequestProcessor
.若是Request對象包含事務數據,該處理器將會接受對ZooKeeper數據樹的修改,不然該處理器會從數據樹中讀取數據並返回給客戶端.
當切換到仲裁模式時,服務器的流水線則有一些變化,下圖展現了羣首操做流水線(類LeaderZooKeeper
)
第一個處理器一樣是PrepRequestProcessor
,而以後的處理器則爲ProposalRequestProcessor
.該處理器會準備一個提議,並將該提議發送給跟隨者. ProposalRequestProcessor
將會把全部請求都轉發給CommitRequestProcessor
,並且對於寫操做請求,還會將請求轉發給SyncRequestProcessor
處理器.
SyncRequestProcessor
處理器所執行的操做與獨立服務器同樣,即持久化操做. 執行完以後會觸發AckRequestProcessor
處理器,它是一個簡單請求處理器,它僅僅生成確認消息並返回給本身. 此處即是上文中提到的,在仲裁模式下,羣首須要收到每一個服務器的確認消息,也包括本身,而AckRequestProcessor
處理器就負責這個;
CommitRequestProcessor
會將收到足夠多的確認消息的提議進行提交. 實際上,確認消息是由Leader類處理的(Leader.processAck()
方法),這個方法會將提交的請求加入到CommitRequestProcessor類中的一個隊列中.這個隊列會由請求處理器線程進行處理.
FinalRequestProcessor
處理器,做用與獨立服務器同樣,不過在它以前還有一個簡單的請求處理器,這個處理器會從提議列表中刪除那些待接受的提議,這個處理器的名字叫ToBeAppliedRequestProcessor
,待接受請求列表包括那些已經被仲裁法定人數所確認的請求,並等待執行. 羣首使用這個列表與追隨者之間進行同步,並將收到確認消息的請求加入到這個列表中. 以後 ToBeAppliedRequestProcessor
處理器就會在 FinalRequestProcessor
處理器執行後刪除這個列表中的元素;
注意: 只有更新請求才會加入到待接受請求列表中,而後由ToBeAppliedRequestProcessor
處理器從該列表刪除.ToBeAppliedRequestProcessor
處理器並不會對讀取請求進行任何額外的處理操做,而是由FinalRequestProcessor
處理器進行操做.
最後來看一下追隨者(FollowerRequestProcessor
類),下圖展現了一個追隨者服務器中會用到的請求處理器:
首先從FollowerRequestProcessor
處理器開始,該處理器接收並處理客戶端請求. FollowerRequestProcessor
處理器以後轉發請求給CommitRequestProcessor
,同時也會轉發寫請求到羣首服務器. CommitRequestProcessor
會直接轉發讀請求到FinalRequestProcessor
處理器, 並且對於寫請求, CommitRequestProcessor
在轉發給 FinalRequestProcessor
處理器以前會等待提交事務.
當羣首接收到一個新的寫請求操做時,直接地或經過其餘追隨者服務器生成一個提議(proposal),以後轉發到追隨者服務器. 當收到一個提議,追隨者會發送這個提議道SyncRequestProcessor
處理器,SendRequestProcessor
會向羣首發送確認消息. 當羣首收到足夠確認消息來提交這個提議時,羣首就會發送提交事務消息給追隨者(同時也會發哦少年宮INFORM消息給觀察者服務器). 當接收到提交事務消息時,追隨者就經過CommitRequestProcessor
處理器進行處理.
爲了保證執行的順序,CommitRequestProcessor
處理器會在收到一個寫請求處理器時暫停後續的請求處理. 這就意味着, 在一個寫請求以後接收到的任何讀取請求都將被阻塞,直到讀取請求轉給CommitRequestProcessor
處理器. 經過等待的方式, 請求能夠被保證按照接收的順序來執行.
小結:
本文討論了ZooKeeper核心機制問題. 羣首競選機制是可用性的關鍵因素,沒有這個機制,ZooKeeper套件將沒法保持可靠性. Zookeeper還須要Zab協議來傳播狀態的更新. 緊接着 闡述了多種服務器類型:包括獨立服務器,羣首服務器,追隨者服務器. 這些服務器之間因運起色制和執行協議的不一樣而不一樣. 在不一樣的部署場景中,各個服務器能夠發揮不一樣的做用, 好比
增長觀察者服務器能夠提供更高的讀吞吐量,並且還不會影響寫吞吐量. 不過增長觀察者服務器並不會增長整個系統的高可用性
.