初識Zookeeper
- zookeeper為分布式應用提供了高效且可靠的分布式協調服務,提供了諸如統一命名服務、配置管理和分布式鎖等分布式的基礎服務。
- 在解決分布式數據一致性方面,zk沒有直接采用Paxos算法,而是采用了一種被稱為ZAB(Zookeeper Atomic Broadcast)的一致性協議。
- zk可以保證如下分布式一致性特性:
- 順序一致性:從同一個client發起的事務請求,最終會被嚴格按照發起順序被應用到zk中。 --> through為每個請求分配一個全局唯一的遞增編號。
- 原子性:所有事務請求的處理結果在整個集群中所有機器上的應用情況一致。
- 單一視圖:無論client連接哪個zk server,其看到的服務端數據模型都是一致的。
- 可靠性:一旦server端成功應用了一個事務,並成功對client響應,那么該事務所引起的服務端狀態變更會被一直保存,知道另一個事務變更。
- 實時性:zk僅保證在一定時間段內,client最終一定能從server上讀取到最新的數據狀態。
zk設計目標
- 簡單的數據模型:zk使得分布式程序能夠通過一個共享的、樹形結構的名字空間來進行相互協調。
- 可以構建集群:集群中只要有超過一半的機器能正常工作,集群就能對外提供服務。client會和集群中任意一台機器建立TCP連接,斷開后,client會自動連接到另一台機器。 --> 可用性up
- 順序訪問:對於client的每個更新請求,zk都會分配一個全局唯一的遞增編號,該編號反應了所有事務操作的先后順序,app可以用zk的這個特性來實現更高層次的同步原語。
- 高性能:zk將全量數據存儲在內存中,並直接服務client的所有非事務請求,因此尤其適用於以讀操作為主的應用場景。
zk的基本概念
集群角色
- 集群角色:最典型的集群模式是Master/Slave模式。能夠處理寫操作的稱為master,所以通過異步復制方式獲取最新數據並提供讀服務的為slave。
- 而zk中沒有沿用傳統的主從模式,而是引入了Leader、Follower和Observer三種角色。
- zk中所有機器通過一個leader選舉過程來選定一台被稱為leader的機器,leader server為client提供讀寫服務。
- follower和observer都能提供讀服務,唯一區別在於observer不參與leader選舉過程,也不參與寫操作的“過半寫成功”策略。因此,observer在不影響寫性能的情況下提升集群的讀性能。
會話(Session)
- 在zk中,一個客戶端連接是指client和server之間的一個TCP長連接。
- 通過這個連接,client可以通過心跳檢測與服務器保持有效的會話,也能夠向zk server發送請求並接受響應。
- Session的 sessionTimeout值用來設置一個客戶端會話的超時時間。當服務器壓力太大、網絡故障或是客戶端主動斷開連接等各種原因導致client連接斷開時,只要在sessionTimeout 時間內能夠重新連接上集群中任意一台服務器,那么之前創建的會話仍然有效。
數據節點(Znode)
- zk中的節點,一類是構建集群的機器,稱為機器節點;另一類則是數據模型中的數據單元,稱為數據節點ZNode。
- zk將所有數據存儲在內存中,數據模型是一棵樹(ZNode Tree)。
- 每個znode上會保存自己的數據內容,同時還會保存一系列屬性信息。
- zk還允許每個節點添加一個特殊的屬性:SEQUENTIAL。該屬性是一個整形數字,是一個由父節點維護的自增數字。
版本
- 對於每個znode,zk都會為其維護一個叫做Stat的數據結構,Stat記錄了該Znode的三個數據版本,分別是version(當前znode的版本)、cversion(當前znode子節點的版本)和aversion(當前znode的ACL版本)。
Watcher
- Watcher(事件監聽器)是zk中一個很重要的特性。zk允許用戶在指定節點上注冊一些watcher,並在一些特定事件觸發時通知感興趣的client。
ACL
- zk采用ACL(Access Control Lists)策略來進行權限控制。
ZK的ZAB協議
ZAB協議
- 事實上,zk並沒有完全采用Paxos算法,而是使用了Zookeeper Atomic Broadcast即zk原子消息廣播協議作為其數據一致性的核心算法。
- ZAB是為zk專門設計的支持崩潰恢復的原子廣播協議。
- zk主要依賴ZAB協議來實現分布式數據的一致性。基於該協議,zk實現了一種主備模式的系統架構來保持集群中各副本之間數據的一致性。
- 具體的,zk使用一個單一的主進程來接收並處理client的所有事務請求,並采用ZAB的原子廣播協議,將server數據的狀態變更以事務Proposal的形式廣播到所有的副本進程上。
- 由於順序執行的狀態變更前后會存在一定的依賴關系,ZAB協議需要保證若一個狀態變更被處理時,所有其依賴的狀態變更都應該已經被處理了。 --> 順序性
- 考慮到主進程可能崩潰,ZAB協議還需要在主進程崩潰時保持正常工作。 --> 高可用性
協議介紹
- ZAB協議包括兩種基本的模式,分別是崩潰恢復和消息廣播。
- 當整個服務框架在啟動過程中,或是當leader服務器出現網絡中斷、崩潰退出與重啟等異常情況時,ZAB協議就會進入恢復模式並選舉產生新的Leader服務器。當選舉產生了新的Leader服務器,同時集群中已有過半的機器(follower)與Leader完成狀態同步(即數據同步,用以保證集群中存在過半的機器與leader的數據狀態保持一致)后,退出恢復模式。
- 過半機器同步完成后,整個服務框架就可以進入消息廣播模式了。啟動后新加入的機器會自覺進入數據恢復模式(即找到leader並進行數據同步)。
- leader server是唯一允許處理事務請求的機器,若其他機器接收到client的事務請求,會將該請求轉發給leader。接着,leader會生成相應的事務提案並發起一輪廣播協議。
消息廣播
- ZAB協議的消息廣播過程使用的是一個原子廣播協議,類似於一個二階段提交過程。
- 當過半的follower反饋Ack之后就可以提交事務Proposal了。
- 但是該模型無法處理leader崩潰退出而帶來的數據不一致問題。因此,在ZAB協議中添加了另一模式,即采用崩潰恢復模式來解決這個問題。
- 另外,整個消息廣播協議是基於具有FIFO特性的TCP協議來進行網絡通信的,因此能夠很容易地保證消息廣播過程中消息接收與發送的順序性。(zk采用單一主線程來處理client請求)
- 在整個消息廣播過程中,leader服務器會為每個事物請求生成對應的Proposal來進行廣播,並且在廣播事務Proposal之前,為proposal分配一個全局單調遞增的唯一ID,即事務ID(ZXID)。
- 具體而言,在消息廣播過程中,leader server會為每個follower分配一個單獨的隊列,然后將需要廣播的事務proposal依次放入這些隊列中,並且根據FIFO策略發送消息。每個follower接收到proposal后,會首先將其以事務日志形式寫入本地磁盤,並且在成功寫入后反饋給leader。當leader收到超過半數的ack后,會廣播一個commit消息給所有的follower以通知其進行事務提交,同時leader自身也會完成對事務的提交,而follower也會在接收到commit消息后,完成對事務的提交。 My Summary: 從上述具體的流程可以看到,整個消息廣播流程本質上是一個2PC過程:
- Phase1: leader發送事務proposal --> follower將proposal以事務形式寫入本地磁盤,並且反饋ack給leader。
- Phase2: leader在收到半數ack后,會廣播一個commit消息給所有follower --> leader完成事務提交 --> follower完成事務的提交。
- 過半follower寫入成功,可以提高系統可用性,即在leader崩潰之后可以選擇數據一致的follower替換當前leader。而只需要過半寫入成功,也可以提高整個過程的效率,縮短阻塞時間。(zk在整個過程中並沒有阻塞,是通過為每個follower維護一個proposal queue,並且用TCP來保證FIFO。)
- 這個過程中,為了保證事務的順序性,為每個事務proposal分配了全局唯一的ZXID,發送端以FIFO的方式發送proposal,並且使用一個單一的主線程以TCP協議發送事務,從這兩個方面保證了事務發送&接收的順序性。
- 除了上述兩個方面,zk還需要解決2PC帶來的單點故障和數據不一致的問題,這就引入了下面一小節要介紹的“崩潰恢復”啦。
崩潰恢復
- zk中一旦leader崩潰(或者可能是由於網絡原因導致leader失去了與過半follower的聯系),那么就會進入崩潰恢復模式。
- 在ZAB協議中,為保證程序的正確運行,整個恢復過程結束后需要選舉出一個新的leader server。因此,ZAB協議需要一個高效且可靠的leader選舉算法。leader選舉算法需要保證leader以及集群中其他機器都知道誰是leader。
- 由上一小節了解到,zk本質上類似於一個2PC的過程,而崩潰恢復就是為了應對2PC存在的“單點故障”和“數據不一致問題”的。
- 其中,“單點故障”是通過leader selection來處理的,也就是說在leader崩潰(失去與過半follower聯系)時會進入崩潰恢復模式。
- 而對於數據不一致問題,我們首先考慮可能產生數據不一致的情況:
在zk處理事務的流程中(具體見上一小節),考慮所有leader可能崩潰的時間點,可以發現:- leader發生proposal后崩潰:則leader有proposal,而follower中沒有(本地磁盤)。
- leader在發送commit消息前崩潰:則leader提交了proposal,而follower未提交。
基本特性
- 在崩潰過程中,可能會出現的兩個數據不一致的隱患及針對這些情況ZAB協議所需要保證的特性。
- ZAB協議需要保證那些已經在leader server上提交的事務最終被所有server都提交。
- 假設一個事務已經得到過半follower的ack,並且已經在leader上被提交了,但是在它將commit消息發送給所有follower之前,leader掛了。
比如,leader先后廣播了P1、P2、C1、P3和C2,其中,當leader將C2(Commit of Proposal2)發出后就立即崩潰退出了,針對這種情況,ZAB協議需要確保proposal2最終能夠在所有的服務器上都被提交成功,否則將出現不一致。
- 假設一個事務已經得到過半follower的ack,並且已經在leader上被提交了,但是在它將commit消息發送給所有follower之前,leader掛了。
- ZAB協議需要確保丟棄那些只在leader server上被提出的事務。
- 相反,如果崩潰恢復過程中出現一個需要被丟棄的提案,那么崩潰恢復結束后需要跳過該事務proposal。
比如,leader在提出了proposal3之后就崩潰退出了,從而導致集群中其他服務器都沒有收到這個事務proposal。於是當server1恢復過來再次加入到集群中時,ZAB協議需要確保丟棄proposal3這個事務。
- 相反,如果崩潰恢復過程中出現一個需要被丟棄的提案,那么崩潰恢復結束后需要跳過該事務proposal。
- 結合上述兩個需求,ZAB協議必須設計一個這樣的leader選舉算法:能夠確保提交已經被leader提交的事務proposal,同時丟棄已經被跳過的事務proposal。因為
- 已經被leader提交的proposal,必定已經被超過半數的follower執行,只是可能還未提交。既然已經被leader提交,為了保證一致性,需要保證所有follower也提交。
- 還未被leader提交的proposal,意味着還沒有超過半數的follower執行。既然leader還未提交,那么必定沒有follower提交,那么可以直接丟棄該proposal。
- Solution:如果leader選舉算法能夠保證新選舉出來的leader服務器擁有集群中所有機器最高編號(即ZXID最大)的事務proposal,那么就可以保證這個新選舉出來的lader一定具有所有已提交的提案。更為重要的是,如果讓具有最高編號事務proposal的機器成為leader,就可以省去leader server檢查proposal的提交和丟棄工作這一操作了。
數據同步
- 完成leader選舉之后,在正式開始工作(即接收客戶端的事務請求,然后提出新的提案)之前,leader server會首先確認事務日志中的所有proposal是否都已經被集群中過半的機器提交了,即是否完成數據同步。
- 下面來看ZAB協議的數據同步過程:
- 首先,leader需要確保所有的follower能夠接收到每一條proposal,並且能正確地將所有已提交的proposal應用到內存數據庫中去。具體的:
- leader為每個follower准備一個隊列,並將那么未被各follower同步的事務以proposal消息的形式逐個發送給follower,並在每個proposal后緊接着一個commit消息,表明該事務已提交
- 等到follower將其所有尚未同步的事務proposal都從leader上同步過來並成功應用到本地數據庫中后,leader會將follower加入到真正的可用follower列表中。
- 首先,leader需要確保所有的follower能夠接收到每一條proposal,並且能正確地將所有已提交的proposal應用到內存數據庫中去。具體的:
- 還需要考慮如何處理那些需要被丟棄的事務proposal(即上面一條處理的需要被所有follower提交的事務):
- ZAB的事務編號ZXID被設計為一個64位的數字,其中低32位可以看作一個簡單的單調遞增的計數器,leader在產生一個新的proposal的時候,都會加一;而高32位則代表了leader周期epoch的編號,每當產生一個新的leader,就會從這個leader上取出起本地日志的最大事務proposal的ZXID,並從該ZXID中解析出對應的epoch值,然后加1作為新的epoch,並將低32位置0來開始生成新的ZXID。從而,ZAB協議可以通過epoch編號來區分leader周期變化的策略,有效避免不同的leader錯誤地使用相同的ZXID編號提出不一樣的proposal的異常情況。
- 基於這樣的策略,當一個包含了上一個leader周期中尚未提交的proposal的server啟動時肯定無法成為leader,因為其不可能擁有最高ZXID的proposal。那么該server會成為follower,當其連接上leader后,會被leader要求進行回退操作,即回退到一個確實已經被集群中過半機器提交的最新的proposal。
深入理解ZAB協議
- 前面的內容介紹了ZAB協議的大體內容以及在實際運行過程中消息廣播和崩潰恢復這兩個基本模式,下面將從系統模型、問題描述、算法描述和運行分析四方面來深入理解ZAB協議。
系統模型
- ZAB協議需要構建的分布式系統模型:
- 在一個由一組進程 {P1, P2, ..., Pn}組成的分布式系統中,其每個進程都有各自的存儲設備,進程間通過相互通信來實現消息的傳遞。
- 每個進程隨時有可能崩潰退出(進入DOWN狀態),在退出后還有可能再次加入(重新進入UP狀態)。
- 集群中存在過半的處於UP狀態的進程組成一個進程子集(Quorum)之后就可以進行正常的消息廣播了。
問題描述
- zk是一個高可用的分布式協調服務,在yahoo的很多大型系統上得到應用。這類應用有個共同的特點,即通常都存在大量的客戶端進程,並且都依賴zk來完成一系列諸如可靠的配置存儲和運行時狀態記錄等分布式協調工作。
- 因而,zk必須具有高吞吐和低延遲的特性,並且能夠很好地在高並發情況下完成分布式數據的一致性處理,同時能夠優雅地處理運行時故障,並具備快速故障恢復的能力。
- ZAB協議時整個zk框架的核心所在,其規定來任何時候都需要保證只有一個主進程負責進行消息廣播,並且在主進程崩潰的時候進行leader選舉。
事務
- 假設各個進程都存在一個類似transaction(v, z)這樣的函數調用,來實現主進程對狀態變更的廣播。主進程每個對transaction(v, z)的調用都包含了:事務內容v和事務標識z,而每個事務標識z又由z = <e, c>兩部分組成,前者是主進程周期e,后者是當前主進程周期內的事務計數c。
- 針對一個新事務,主進程首先遞增事務計數c。
算法描述
- 下面將從算數描述角度來深入理解ZAB協議的內部原理。
- 整個ZAB協議主要包括消息廣播和崩潰恢復兩個過程。進一步可以細分為發現(discovery)、同步(synchronization)和廣播(broadcast)三個階段。組成ZAB協議的每一個分布式進程,會循環地執行這三個階段,這樣的一個循環稱為一個主進程周期。
- 首先,給出ZAB協議中一些專有術語的定義
階段一:發現
- 階段一主要就是leader選舉過程,用於在多個分布式進程中選舉出主進程,准leader L和follower F的工作流程如下:
- 步驟F.1.1 follower將自己最后接受的事務proposal的epoch值CEPOCH(F.p)發送給准leader L
- 步驟L.1.1 當接收到過半follower的CEPOCH(F.p)消息后,准Leader L會生成NEWEPOCH(e')消息給這些過半的follower
- 步驟 F.1.2 當follower接收到來自准leader L的NEWPOCH(e')后,如果其檢測到當前的CEPOCH(F.p)值小於e',那么就會將CEPOCH(F.p)賦值為e', 同時向准leader L發送ack。這個反饋消息(ACK-E(F.p, hf))中,包含了當前該follower的epoch CEPOCH(F.p)以及該follower的歷史proposal集合: hf。
- 當leader L收到來自過半follower的ack后,L就會從這過半服務器中選舉一個follower,使用其作為初始化事務集合Ie'。
- 選取方式:對Quorum中其他任意一個Follower F',F需要滿足以下兩個條件之一:
- 選取方式:對Quorum中其他任意一個Follower F',F需要滿足以下兩個條件之一:
- 至此,ZAB協議完成階段一的工作流程。
階段二:同步
- 步驟 L.2.1 Leader L會將e' 和 Ie' 以NEWLEADER(e', Ie')消息的形式發送給所有Quorum中的follower。
- 步驟 F.2.2 當follower接收到來自L的NEWLEADER(e', Ie')消息后,若follower發現CEPOCH(F.p) 不等於 e',那么直接進入下一輪循環。若想等,則follower會執行事務應用操作。具體的,對於每個事務Proposal:<v, z>屬於Ie',follower會接收<e', <v, z>>。最后反饋給leader,表明自己已經接受並處理了所有Ie'中的事務proposal。
- 步驟 L2.2 當leader接收到來自過半follower針對NEWLEADER(e', Ie')的ack后,就會向所有follower發commit消息。至此leader完成階段二。
- 當follower收到leader的commit消息后,會依次處理並提交所有在Ie''中未處理的事務。至此follower完成階段二。
階段三:廣播
- 完成同步之后,ZAB協議就可以正式開始接受客戶端的事務請求,並進行消息廣播流程。
- 步驟 L.3.1 Leader L接收到client新的事務請求后,會生成proposal,並根據ZXID的順序向所有follower發送提案<e', <v, z>>,其中epoch(z) = e'。
- 步驟 F.3.1 follower根據消息接收的先后次序來處理這些來自leader的事務proposal,並將他們追加到hf中去,之后反饋給leader。
- 步驟 L.3.1 當leader接收到過半follower的針對proposal <e', <v, z>>的ack后,就會發送commit<e', <v, z>>消息給所有follower。
- 步驟 F.3.2 當follower F接收到來自leader的commit<e', <v, z>>后,會開始提交proposal<e', <v, z>>。需要注意的是,此時follower F必定已經提交了事務proposal <v', z'>, 其中<v', z'> 屬於hf,z' < z。
- 在正常運行過程中,ZAB協議會一直運行於階段三來反復地進行消息廣播流程。如果出現leader崩潰或其他原因導致leader缺失,ZAB會再次進入階段一。
運行分析
- 在ZAB協議的設計中,每個進程都可能處於以下三種狀態之一:
- LOOKING: leader選舉階段
- FOLLOWING: follower和leader保持同步狀態
- LEADING: leader作為主進程領導狀態
ZAB與Paxos算法的聯系與區別
- ZAB協議並不是Paxos算法的一個典型實現。
- 兩者的聯系:
- 兩者都存在一個類似於leader進程的角色,由其負責協調多個follower進程的運行;
- leader進程會等待超過半數的follower作出正確的反饋后,才會將一個提案進行提交;
- 在ZAB協議中,每個proposal都包含一個epoch值,用以代表當前的leader周期,在Paxos算法中,同樣存在一個這樣的標識,只是名字變成了Ballot。
- 在Paxos算法中,一個新選舉的主進程會進行兩個階段的工作,第一階段被稱為讀階段,在這個階段中,這個新的主進程會通過和所有其他進程進行通信的方式來收集上一個主進程提出的提案,並將它們提交。第二階段稱為寫階段,在這個階段,當前主進程開始提出自己的提案。
- ZAB協議在Paxos基礎上,ZAB額外添加了一個同步階段。在同步階段之前,ZAB協議也存在一個和Paxos讀階段非常類似的過程,即發現階段。在同步階段,新的leader會確保存在過半的follower已經提交了之前leader周期中的所有事務proposal。一旦同步完成之后,ZAB就會執行和Paxos類似的寫階段。
- 總的來說,ZAB和Paxos算法的本質區別在於,兩者的設計目標不太一樣。ZAB主要用於構建一個高可用的分布式數據主備系統,例如zk。而Paxos算法則是用於構建一個分布式的一致性狀態機系統。