分析Zookeeper的一致性原理

blob.png


  zookeeper(簡稱zk),顧名思義,爲動物園管理員的意思,動物對應服務節點,zk是這些節點的管理者。在分佈式場景中,zk的應用很是普遍,如:數據發佈/訂閱、命名服務、配置中心、分佈式鎖、集羣管理、選主與服務發現等等。這不只得益於zk類文件系統的數據模型基於Watcher機制的分佈式事件通知,也得益於zk特殊的高容錯數據一致性協議
       這裏的一致性,是指數據在多個副本之間保持一致的特性。分佈式環境裏,多個副本處於不一樣的節點上,若是對副本A的更新操做,未同步到副本B上,外界獲取數據時,A與B的返回結果會不同,這是典型的分佈式數據不一致狀況。而強一致性,是指分佈式系統中,若是某個數據更新成功,則全部用戶都能讀取到最新的值。CAP定理告訴咱們,在分佈式系統設計中,P(分區容錯性)是不可缺乏的,所以只能在A(可用性)與C(一致性)間作取捨。本文主要探究zk在數據一致性方面的處理邏輯。

1、基本概念:
算法

  • 數據節點(dataNode):zk數據模型中的最小數據單元,數據模型是一棵樹,由斜槓(/)分割的路徑名惟一標識,數據節點能夠存儲數據內容及一系列屬性信息,同時還能夠掛載子節點,構成一個層次化的命名空間。數據庫

  • 會話(Session):指zk客戶端與zk服務器之間的會話,在zk中,會話是經過客戶端和服務器之間的一個TCP長鏈接來實現的。經過這個長鏈接,客戶端可以使用心跳檢測與服務器保持有效的會話,也能向服務器發送請求並接收響應,還可接收服務器的Watcher事件通知。Session的sessionTimeout,是會話超時時間,若是這段時間內,客戶端未與服務器發生任何溝通(心跳或請求),服務器端會清除該session數據,客戶端的TCP長鏈接將不可用,這種狀況下,客戶端須要從新實例化一個Zookeeper對象。apache

  • 事務及ZXID:事務是指可以改變Zookeeper服務器狀態的操做,通常包括數據節點的建立與刪除、數據節點內容更新和客戶端會話建立與失效等操做。對於每一個事務請求,zk都會爲其分配一個全局惟一的事務ID,即ZXID,是一個64位的數字,高32位表示該事務發生的集羣選舉週期(集羣每發生一次leader選舉,值加1),低32位表示該事務在當前選擇週期內的遞增次序(leader每處理一個事務請求,值加1,發生一次leader選擇,低32位要清0)。緩存

  • 事務日誌:全部事務操做都是須要記錄到日誌文件中的,可經過 dataLogDir配置文件目錄,文件是以寫入的第一條事務zxid爲後綴,方便後續的定位查找。zk會採起「磁盤空間預分配」的策略,來避免磁盤Seek頻率,提高zk服務器對事務請求的影響能力。默認設置下,每次事務日誌寫入操做都會實時刷入磁盤,也能夠設置成非實時(寫到內存文件流,定時批量寫入磁盤),但那樣斷電時會帶來丟失數據的風險。安全

  • 數據快照:數據快照是zk數據存儲中另外一個很是核心的運行機制。數據快照用來記錄zk服務器上某一時刻的全量內存數據內容,並將其寫入到指定的磁盤文件中,可經過dataDir配置文件目錄。可配置參數snapCount,設置兩次快照之間的事務操做個數,zk節點記錄完事務日誌時,會統計判斷是否須要作數據快照(距離上次快照,事務操做次數等於snapCount/2~snapCount 中的某個值時,會觸發快照生成操做,隨機值是爲了不全部節點同時生成快照,致使集羣影響緩慢)。服務器

  • 過半:所謂「過半」是指大於集羣機器數量的一半,即大於或等於(n/2+1),此處的「集羣機器數量」不包括observer角色節點。leader廣播一個事務消息後,當收到半數以上的ack信息時,就認爲集羣中全部節點都收到了消息,而後leader就不須要再等待剩餘節點的ack,直接廣播commit消息,提交事務。選舉中的投票提議及數據同步時,也是如此,leader不須要等到全部learner節點的反饋,只要收到過半的反饋就可進行下一步操做。網絡

2、數據模型
       zk維護的數據主要有:客戶端的會話(session)狀態及數據節點(dataNode)信息。zk在內存中構造了個DataTree的數據結構,維護着path到dataNode的映射以及dataNode間的樹狀層級關係。爲了提升讀取性能,集羣中每一個服務節點都是將數據全量存儲在內存中。可見,zk最適於讀多寫少且輕量級數據(默認設置下單個dataNode限制爲1MB大小)的應用場景。數據僅存儲在內存是很不安全的,zk採用事務日誌文件及快照文件的方案來落盤數據,保障數據在不丟失的狀況下能快速恢復。

3、集羣架構
blob.png       zk集羣由多個節點組成,其中有且僅有一個leader,處理全部事務請求;follower及observer統稱learner。learner須要同步leader的數據。follower還參與選舉及事務決策過程。zk客戶端會打散配置文件中的serverAddress 順序並隨機組成新的list,而後循環按序取一個服務器地址進行鏈接,直到成功。follower及observer會將事務請求轉交給leader處理。        
blob.pngsession

     要搭建一個高可用的zk集羣,咱們首先須要肯定好集羣規模。通常咱們將節點(指leader及follower節點,不包括observer節點)個數設置爲 2*n+1 ,n爲可容忍宕機的個數。 zk使用「過半」設計原則,很好地解決了單點問題,提高了集羣容災能力。可是zk的集羣伸縮不是很靈活,集羣中全部機器ip及port都是事先配置在每一個服務的zoo.cfg 文件裏的。若是要往集羣增長一個follower節點,首先須要更改全部機器的zoo.cfg,而後逐個重啓。

集羣模式下,單個zk服務節點啓動時的工做流程大致以下:
數據結構

  • 統一由QuorumPeerMain做爲啓動類,加載解析zoo.cfg配置文件;
    架構

  • 初始化核心類:ServerCnxnFactory(IO操做)、FileTxnSnapLog(事務日誌及快照文件操做)、QuorumPeer實例(表明zk集羣中的一臺機器)、ZKDatabase(內存數據庫)等;

  • 加載本地快照文件及事務日誌,恢復內存數據;

  • 完成leader選舉,節點間經過一系列投票,選舉產生最合適的機器成爲leader,同時其他機器成爲follower或是observer。關於選舉算法,就是集羣中哪一個機器處理的數據越新(經過ZXID來比較,ZXID越大,數據越新),其越有可能被選中;

  • 完成leader與learner間的數據同步:集羣中節點角色肯定後,leader會從新加載本地快照及日誌文件,以此做爲基準數據,再結合各個learner的本地提交數據,leader再肯定須要給具體learner回滾哪些數據及同步哪些數據;

  • 當leader收到過半的learner完成數據同步的ACK,集羣開始正常工做,能夠接收並處理客戶端請求,在此以前集羣不可用。

4、zookeeper一致性協議
       zookeeper實現數據一致性的核心是ZAB協議(Zookeeper原子消息廣播協議)。該協議須要作到如下幾點:
(1)集羣在半數如下節點宕機的狀況下,能正常對外提供服務;
(2)客戶端的寫請求所有轉交給leader來處理,leader需確保寫變動能實時同步給全部follower及observer;
(3)leader宕機或整個集羣重啓時,須要確保那些已經在leader服務器上提交的事務最終被全部服務器都提交,確保丟棄那些只在leader服務器上被提出的事務,並保證集羣能快速恢復到故障前的狀態。
       Zab協議有兩種模式, 崩潰恢復(選主+數據同步)和消息廣播(事務操做)。任什麼時候候都須要保證只有一個主進程負責進行事務操做,而若是主進程崩潰了,就須要迅速選舉出一個新的主進程。主進程的選舉機制與事務操做機制是緊密相關的。下面詳細講解這三個場景的協議規則,從細節去探索ZAB協議的數據一致性原理。

一、選主:leader選舉是zk中最重要的技術之一,也是保證分佈式數據一致性的關鍵所在。當集羣中的一臺服務器處於以下兩種狀況之一時,就會進入leader選舉階段——服務器初始化啓動、服務器運行期間沒法與leader保持鏈接。
選舉階段,集羣間互傳的消息稱爲投票,投票Vote主要包括二個維度的信息:ID、ZXID

  • ID   被推舉的leader的服務器ID,集羣中的每一個zk節點啓動前就要配置好這個全局惟一的ID。

  • ZXID  被推舉的leader的事務ID ,該值是從機器DataTree內存中取的,即事務已經在機器上被commit過了。

節點進入選舉階段後的大致執行邏輯以下:
       (1)設置狀態爲LOOKING,初始化內部投票Vote (id,zxid) 數據至內存,並將其廣播到集羣其它節點。節點首次投票都是選舉本身做爲leader,將自身的服務ID、處理的最近一個事務請求的ZXID(ZXID是從內存數據庫裏取的,即該節點最近一個完成commit的事務id)及當前狀態廣播出去。而後進入循環等待及處理其它節點的投票信息的流程中。
       (2)循環等待流程中,節點每收到一個外部的Vote信息,都須要將其與本身內存Vote數據進行PK,規則爲取ZXID大的,若ZXID相等,則取ID大的那個投票。若外部投票勝選,節點須要將該選票覆蓋以前的內存Vote數據,並再次廣播出去;同時還要統計是否有過半的贊同者與新的內存投票數據一致,無則繼續循環等待新的投票,有則須要判斷leader是否在贊同者之中,在則退出循環,選舉結束,根據選舉結果及各自角色切換狀態,leader切換成LEADING、follower切換到FOLLOWING、observer切換到OBSERVING狀態。

       算法細節可參照FastLeaderElection.lookForLeader(),主要有三個線程在工做:選舉線程(主動調用lookForLeader方法的線程,經過阻塞隊列sendqueue及recvqueue與其它兩個線程協做)、WorkerReceiver線程(選票接收器,不斷獲取其它服務器發來的選舉消息,篩選後會保存到recvqueue隊列中。zk服務器啓動時,開始正常工做,不中止)以及WorkerSender線程(選票發送器,會不斷地從sendqueue隊列中獲取待發送的選票,並廣播至集羣)。WorkerReceiver線程一直在工做,即便當前節點處於LEADING或者FOLLOWING狀態,它起到了一個過濾的做用,當前節點爲LOOKING時,纔會將外部投票信息轉交給選舉線程處理;若是當前節點處於非LOOKING狀態,收到了處於LOOKING狀態的節點投票數據(外部節點重啓或網絡抖動狀況下),說明發起投票的節點數據跟集羣不一致,這時,當前節點須要向集羣廣播出最新的內存Vote(id,zxid),落後節點收到該Vote後,會及時註冊到leader上,並完成數據同步,跟上集羣節奏,提供正常服務。

二、選主後的數據同步:選主算法中的zxid是從內存數據庫中取的最新事務id,事務操做是分兩階段的(提出階段和提交階段),leader生成提議並廣播給followers,收到半數以上的ACK後,再廣播commit消息,同時將事務操做應用到內存中。follower收到提議後先將事務寫到本地事務日誌,而後反饋ACK,等接到leader的commit消息時,纔會將事務操做應用到內存中。可見,選主只是選出了內存數據是最新的節點,僅僅靠這個是沒法保證已經在leader服務器上提交的事務最終被全部服務器都提交。好比leader發起提議P1,並收到半數以上follower關於P1的ACK後,在廣播commit消息以前宕機了,選舉產生的新leader以前是follower,未收到關於P1的commit消息,內存中是沒有P1的數據。而ZAB協議的設計是須要保證選主後,P1是須要應用到集羣中的。這塊的邏輯是經過選主後的數據同步來彌補。
       選主後,節點須要切換狀態,leader切換成LEADING狀態後的流程以下:
(1)從新加載本地磁盤上的數據快照至內存,並從日誌文件中取出快照以後的全部事務操做,逐條應用至內存,並添加到已提交事務緩存commitedProposals。這樣能保證日誌文件中的事務操做,一定會應用到leader的內存數據庫中。
(2)獲取learner發送的FOLLOWERINFO/OBSERVERINFO信息,並與自身commitedProposals比對,肯定採用哪一種同步方式,不一樣的learner可能採用不一樣同步方式(DIFF同步、TRUNC+DIFF同步、SNAP同步)。這裏是拿learner內存中的zxid與leader內存中的commitedProposals(min、max)比對,若是zxid介於min與max之間,但又不存在於commitedProposals中時,說明該zxid對應的事務須要TRUNC回滾;若是 zxid 介於min與max之間且存在於commitedProposals中,則leader須要將zxid+1~max 間全部事務同步給learner,這些內存缺失數據,極可能是由於leader切換過程當中形成commit消息丟失,learner只完成了事務日誌寫入,未完成提交事務,未應用到內存。
(3)leader主動向全部learner發送同步數據消息,每一個learner有本身的發送隊列,互不干擾。同步結束時,leader會向learner發送NEWLEADER指令,同時learner會反饋一個ACK。當leader接收到來自learner的ACK消息後,就認爲當前learner已經完成了數據同步,同時進入「過半策略」等待階段。當leader統計到收到了一半已上的ACK時,會向全部已經完成數據同步的learner發送一個UPTODATE指令,用來通知learner集羣已經完成了數據同步,能夠對外服務了。
        細節可參照Leader.lead() 、Follower.followLeader()及LearnerHandler類。

三、事務操做:ZAB協議對於事務操做的處理是一個相似於二階段提交過程。針對客戶端的事務請求,leader服務器會爲其生成對應的事務proposal,並將其發送給集羣中全部follower機器,而後收集各自的選票,最後進行事務提交。流程以下圖。


blob.png

ZAB協議的二階段提交過程當中,移除了中斷邏輯(事務回滾),全部follower服務器要麼正常反饋leader提出的事務proposal,要麼就拋棄leader服務器。follower收到proposal後的處理很簡單,將該proposal寫入到事務日誌,而後立馬反饋ACK給leader,也就是說若是不是網絡、內存或磁盤等問題,follower確定會寫入成功,並正常反饋ACK。leader收到過半follower的ACK後,會廣播commit消息給全部learner,並將事務應用到內存;learner收到commit消息後會將事務應用到內存。

ZAB協議中屢次用到「過半」設計策略 ,該策略是zk在A(可用性)與C(一致性)間作的取捨,也是zk具備高容錯特性的本質。相較分佈式事務中的2PC(二階段提交協議)的「全量經過」,ZAB協議可用性更高(犧牲了部分一致性),能在集羣半數如下服務宕機時正常對外提供服務。


5、參考:

《從paxos到Zookeeper分佈式一致性原理與實踐》、org.apache.zookeeper:zookeeper:3.4.0

相關文章
相關標籤/搜索