zookeeper核心原理全面解析

  下述各zookeeper機制的java客戶端實踐參考zookeeper java客戶端之curator詳解html

  官方文檔http://zookeeper.apache.org/doc/current/zookeeperOver.html、http://zookeeper.apache.org/doc/current/zookeeperInternals.html描述了部分關於zk的內部工做機制,可是並不夠友好和詳細。java

zookeeper簡介

  據官網介紹,ZooKeeper是一個用於提供配置信息、命名服務、分佈式協調以及分組服務的中心化服務,它們是分佈式應用所必需的。從實際應用來看,zookeeper是最普遍使用的分佈式協調服務,包括dubbo、kafka、hadoop、es-job等都依賴於zookeeper提供的分佈式協調和註冊服務。其餘用於提供註冊服務的中間件還包括consul以及etcd、eureka,但都不及zookeeper普遍。其餘適用場景可見https://xie.infoq.cn/article/1dcd3f8b100645e0da782a279node

  官網:https://zookeeper.apache.org/,https://zookeeper.apache.org/doc/r3.4.14/mysql

  zookeeper配置文件詳解:https://zookeeper.apache.org/doc/r3.4.14/zookeeperAdmin.html#sc_configuration,也能夠參考http://www.javashuo.com/article/p-seepotrk-z.htmlgit

核心機制

zookeeper節點角色

  在zookeeper中,節點分爲下列幾種角色:github

  • 領導者(leader),負責進行投票的發起和決議,更新系統狀態,在Zookeeper集羣中,只有一個Leader節點。
  • 學習者(learner),包括跟隨者(follower)和觀察者(observer)。
  • follower用於接受客戶端請求並想客戶端返回結果,在選主過程當中參與投票,在Zookeeper集羣中,follower能夠爲多個。
  • Observer能夠接受客戶端鏈接,將寫請求轉發給leader,但observer不參加投票過程,只同步leader的狀態,observer的目的是爲了擴展系統,提升讀取速度(不參與投票下降選舉的複雜度)

在一個zookeeper集羣,各節點之間的交互以下所示:web

 注:幾乎全部現代基於分佈式架構的中間件都是採用相似作法,例如kafka、es等。redis

 從上可知,全部請求均由客戶端發起,它多是本地zkCli或java客戶端。 各角色詳細職責以下。算法

Leader

  leader的職責包括:sql

    • 恢復數據;
    • 維持與Learner的心跳,接收Learner請求並判斷Learner的請求消息類型;
      Leader的工做流程簡圖以下所示,在實際實現中,啓動了三個線程來實現功能。

 

Follower

  follower的主要職責爲:

  • 向Leader發送請求;
  • 接收Leader的消息並進行處理;
  • 接收Zookeeper Client的請求,若是爲寫清求,轉發給Leader進行處理

  Follower的工做流程簡圖以下所示,在實際實現中,Follower是經過5個線程來實現功能的。

  各類消息的含義以下: 

PING:心跳消息。
PROPOSAL:Leader發起的提案,要求Follower投票。
COMMIT:服務器端最新一次提案的信息。
UPTODATE:代表同步完成。
REVALIDATE:根據Leader的REVALIDATE結果,關閉待revalidate的session仍是容許其接受消息。
SYNC:返回SYNC結果到客戶端,這個消息最初由客戶端發起,用來強制獲得最新的更新。

zookeeper數據存儲機制

  雖然zookeeper採用的是文件系統存儲機制,可是全部數據數據都存儲於內存中。其對外提供的視圖相似於Unix文件系統。樹的根Znode節點至關於Unix文件系統的根路徑。

  

節點類型

  zk中的節點稱之爲znode(也叫data register,也就是存儲數據的文件夾),按其生命週期的長短能夠分爲持久結點(PERSISTENT)和臨時結點(EPHEMERAL);在建立時還可選擇是否由Zookeeper服務端在其路徑後添加一串序號用來區分同一個父結點下多個結點建立的前後順序。
  通過組合就有如下4種Znode結點類型:

  • persistent:永久性znode。
  • ephemeral: 隨着建立的客戶端關閉而自動刪除,不過它們仍然對全部客戶端可見,ephemeral節點不容許有子節點。是實現分佈式協調的核心機制。
  • sequential:附屬於上述兩類節點,是一種特性。在建立時,zookeeper會在其名字上分配一個序列號。能夠做爲全局分佈式隊列使用。以下:

         

zookeeper的一致性保證

  zookeeper經過下列機制實現一致性保證:

  » 全部更新請求順序進行,來自同一個client的更新請求按其發送順序依次執行
  » 數據更新原子性,一次數據更新要麼成功,要麼失敗
  » 全局惟一數據視圖,client不管鏈接到哪一個server,數據視圖都是一致的,基於全部寫請求所有由leader完成,而後同步實現
  » 實時性,在必定事件範圍內,client能讀到最新數據

讀寫機制

» Zookeeper是一個由多個server組成的集羣
  » 一個leader,多個follower
  » 每一個server保存一份數據副本
  » 全局數據一致
  » 分佈式讀寫
  » 更新請求所有轉發由leader完成,並在成功後同步給follower
客戶端寫請求的過程以下:

  其過程爲:

  • 1.全部的事務請求都交由集羣的Leader服務器來處理,Leader服務器會將一個事務請求轉換成一個Proposal(提議),併爲其生成一個全局遞增的惟一ID,這個ID就是事務ID,即ZXID,Leader服務器對Proposal是按其ZXID的前後順序來進行排序和處理的。
  • 2.以後Leader服務器會將Proposal放入每一個Follower對應的隊列中(Leader會爲每一個Follower分配一個單獨的隊列),並以FIFO的方式發送給Follower服務器。
  • 3.Follower服務器接收到事務Proposal後,首先以事務日誌的方式寫入本地磁盤,而且在成功後返回Leader服務器一個ACK響應。
  • 4.Leader服務器只要收到過半Follower的ACK響應,就會廣播一個Commit消息給Follower以通知其進行Proposal的提交,同時Leader自身也會完成Proposal的提交。

  因爲每次的請求都須要轉發給leader並進行投票處理,因此zookeeper並不適合於寫密集型的場景,例如序列產生器、分佈式鎖,不一樣節點數量、不一樣讀寫比例下zk的tps以下:

 

   來源於官方測試。上述測試基於3.2,2Ghz Xeon, 2塊SATA 15K RPM硬盤。日誌(WAL)在單獨的硬盤,快照(zk內存數據快照)寫在OS系統盤,讀寫分別爲1K大小,且客戶端不直連leader。且從上可知,節點越多、寫越慢、讀越快,因此通常節點不會不少,可是爲了作擴展性和異地,會使用observer節點。 

dataDir=/data

dataLogDir=/datalog

dataLogDir若是沒提供的話使用的則是dataDir。zookeeper的持久化都存儲在這兩個目錄裏。dataLogDir裏是放到的順序日誌(WAL)。而dataDir裏放的是內存數據結構的snapshot,便於快速恢復(目前基本上全部帶持久化特性的中間件如redis 4.x(kafka採用磁盤append,是個另類)都是借鑑數據庫(oracle/mysql也支持buffer_pool/sga的dump)的作法,按期快照+WAL重放,而不是重啓後清空來儘量提高性能)。爲了達到性能最大化,通常建議把dataDir和dataLogDir分到不一樣的磁盤上,這樣就能夠充分利用磁盤順序寫的特性。以下:

 

 zookeeper快照文件的命名規則爲snapshot.**,其中**表示zookeeper觸發快照的那個瞬間,提交的最後一個事務的ID。其默認不會清理,從3.4.0開始,zookeeper提供了自動清理snapshot和事務日誌的功能,經過配置 autopurge.snapRetainCount 和 autopurge.purgeInterval 這兩個參數可以實現定時清理了。這兩個參數都是在zoo.cfg中配置的:autopurge.purgeInterval 這個參數指定了清理頻率,單位是小時,須要填寫一個1或更大的整數,默認是0,表示不開啓本身清理功能。autopurge.snapRetainCount 這個參數和上面的參數搭配使用,這個參數指定了須要保留的文件數目。默認是保留3個。

zkid

  znode節點的狀態信息中包含czxid, 那麼什麼是zxid呢? 在zk中,狀態的每一次改變, 都對應着一個遞增的Transaction id, 該id稱爲zxid. 因爲zxid的遞增性質, 若是zxid1小於zxid2, 那麼zxid1確定先於zxid2發生.    建立任意節點, 或者更新任意節點的數據, 或者刪除任意節點, 都會致使Zookeeper狀態發生改變, 從而致使zxid的值增長.

znode節點中的信息

  Znode結構主要由存儲於其中的數據信息和狀態信息兩部分構成,經過get 命令獲取一個Znode結點的信息以下:

  第一行存儲的是ZNode的數據信息,從cZxid開始就是Znode的狀態信息。Znode的狀態信息比較多,幾個主要的爲:

    • czxid:
      即Created ZXID,表示建立該Znode結點的事務ID

    • mzxid:
      即Modified ZXID,表示最後一次更新該結點的事務ID

    • version
      該Znode結點的版本號。每一個Znode結點被建立時版本號都爲0,每更新一次都會致使版本號加1,即便更新先後Znode存儲的值沒有變化版本號也會加1。version值能夠形象的理解爲Znode結點被更新的次數。Znode狀態信息中的版本號信息,使得服務端能夠對多個客戶端對同一個Znode的更新操做作併發控制。整個過程和java中的CAS有點像,是一種樂觀鎖的併發控制策略,而version值起到了衝突檢測的功能。客戶端拿到Znode的version信息,並在更新時附上這個version信息,服務端在更新Znode時必須必須比較客戶端的version和Znode的實際version,只有這兩個version一致時纔會進行修改。

  ZooKeeper默認狀況下對數據字段的傳輸限制爲1MB(全部分佈式應用幾乎默認都這個大小,如kafka、dubbo),該限制爲任何節點數據字段的最大可存儲字節數,同時也限制了任何父節點能夠擁有的子節點數。

zookeeper的其餘核心機制

  • Zookeeper的核心是原子廣播,這個機制保證了各個Server之間的同步。實現這個機制的協議叫作Zab協議。Zab協議有兩種模式,它們分別是恢復模式(選主)和廣播模式(同步)。當服務啓動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數Server完成了和leader的狀態同步之後,恢復模式就結束了。狀態同步保證了leader和Server具備相同的系統狀態。
  • 爲了保證事務的順序一致性,zookeeper採用了遞增的事務id號(zxid)來標識事務。全部的提議(proposal)都在被提出的時候加上了zxid。實現中zxid是一個64位的數字,它高32位是epoch用來標識leader關係是否改變,每次一個leader被選出來,它都會有一個新的epoch,標識當前屬於那個leader的統治時期。低32位用於遞增計數。
  • 每一個Server在工做過程當中有三種狀態:
    LOOKING:當前Server不知道leader是誰,正在搜尋
    LEADING:當前Server即爲選舉出來的leader
    FOLLOWING:leader已經選舉出來,當前Server與之同步

分佈式系統的一致性實現算法之paxos

  paxos算法基於這樣的原理:

  • 在一個分佈式數據庫系統中,若是各節點的初始狀態一致,每一個節點都執行相同的操做序列,那麼他們最後能獲得一個一致的狀態。
  • Paxos算法解決的什麼問題呢,解決的就是保證每一個節點執行相同的操做序列。好吧,這還不簡單,master維護一個
     全局寫隊列,全部寫操做都必須 放入這個隊列編號,那麼不管咱們寫多少個節點,只要寫操做是按編號來的,就能保證一
   致性。沒錯,就是這樣,但是若是master掛了呢。
  • Paxos算法經過投票來對寫操做進行全局編號,同一時刻,只有一個寫操做被批准,同時併發的寫操做要去爭取選票,
   只有得到過半數選票的寫操做纔會被 批准(因此永遠只會有一個寫操做獲得批准),其餘的寫操做競爭失敗只好再發起一
   輪投票,就這樣,在日復一日年復一年的投票中,全部寫操做都被嚴格編號排 序。編號嚴格遞增,當一個節點接受了一個
   編號爲100的寫操做,以後又接受到編號爲99的寫操做(由於網絡延遲等不少不可預見緣由),它立刻能意識到本身 數據
   不一致了,自動中止對外服務並重啓同步過程。任何一個節點掛掉都不會影響整個集羣的數據一致性(總2n+1臺,除非掛掉大於n臺)
所以在生產中,要求zookeeper部署3(單機房)或5(單機房或多機房)或7(跨機房)個節點的集羣。

zookeeper java官方客戶端核心package簡介

  • org.apache.zookeeper 包含ZooKeeper客戶端主要類,ZooKeeper watch和各類回調接口的定義。
  • org.apache.zookeeper.data 定義了和data register相關的特性
  • org.apache.zookeeper.server, org.apache.zookeeper.server.quorum, org.apache.zookeeper.server.upgrade是服務器實現的核心接口
  • org.apache.zookeeper.client定義了Four Letter Word的主要類

  因爲zookeeper的java官方客戶端太不友好,所以實際中通常使用三方客戶端Curator。故不對zookeeper客戶端進行詳細分析,參見本文首部對curator的詳解。

watch機制

  watch是zookeeper針對節點的一次性觀察者機制(即一次觸發後就失效,須要手工從新建立watch),行爲上相似於數據庫的觸發器。

  當watch監視的數據發生時,通知設置了該watch的client,客戶端即watcher。watcher的機制是監聽數據發生了某些變化,因此必定會有對應的事件類型和狀態類型,一個客戶端能夠監控多個節點,在代碼中體如今new了幾個就產生幾個watcher,只要節點變化都會執行一遍process。其示意圖以下:

  在zookeeper中,watch是採用推送機制實現的,而不是客戶端輪訓(有些中間件採用拉的模式,例如kafka消費者,這主要取決於設計者認爲的合理性,通常來講流量很大的適合於拉的模式,這樣更好作控制,不然客戶端容易失控;反之推的模式)。watch有兩種類型的事件可以監聽:znode相關的及客戶端實例相關的。分別爲:

  • 事件類型:(znode節點相關的)【針對的是你所觀察的一個節點而言的】
    • EventType.NodeCreated 【節點建立】
    • EventType.NodeDataChanged 【節點數據發生變化】
    • EventType.NodeChildrenChanged 【這個節點的子節點發生變化】
    • EventType.NodeDeleted 【刪除當前節點】
  • 狀態類型:(是跟客戶端實例相關的)【ZooKeeper集羣跟應用服務之間的狀態的變動】
    • KeeperState.Disconnected 【沒有鏈接上】
    • KeeperState.SyncConnected 【鏈接上】
    • KeeperState.AuthFailed 【認證失敗】
    • KeeperState.Expired 【過時】

  總結起來,zk watch的特性爲:

  • 一次性:對於ZooKeeper的watcher,你只須要記住一點,ZooKeeper有watch事件,是一次性觸發的,當watch監視的數據發生變化時,通知設置該watch的client,即watcher,因爲ZooKeeper的監控都是一次性的,因此每次必須設置監控。在這裏,LZ不得不說一句,一次觸發實際上是一個特性、並不是設計缺陷,且zk各api已經提供了開關是否繼續開啓,並無帶來不方便,因此把它作缺點是說不過去的。
  • 客戶端串行執行:客戶端watcher回調的過程是一個串行同步的過程,這爲咱們保證了順序,同時須要開發人員注意一點,千萬不要由於一個watcher的處理邏輯影響了整個客戶端的watcher回調
  • 輕量:WatchedEvent是ZooKeeper整個Watcher通知機制的最小通知單元,整個結構只包含三個部分:通知狀態、事件類型和節點路徑。也就是說Watcher通知很是的簡單,只會告知客戶端發生了事件而不會告知其具體內容,須要客戶端本身去進行獲取,好比NodeDataChanged事件,ZooKeeper只會通知客戶端指定節點的數據發生了變動,而不會直接提供具體的數據內容
  • 客戶端設置的每一個監視點與會話關聯,若是會話過時,等待中的監視點將會被刪除。不過監視點能夠跨越不一樣服務端的鏈接而保持,例如,當一個ZooKeeper客戶端與一個ZooKeeper服務端的鏈接斷開後鏈接到集合中的另外一個服務端,客戶端會發送未觸發的監視點列表,在註冊監視點時,服務端將要檢查已監視的znode節點在以前註冊監視點以後是否已經變化,若是znode節點已經發生變化,一個監視點的事件就會被髮送給客戶端,不然在新的服務端上註冊監視點。這一機制使得咱們能夠關心邏輯層會話,而非底層鏈接自己。

LEADER服務器的選舉 

  兩種狀況下會發生Leader節點的選舉,集羣初始構建的時候;其次,不管如何,leader老是有可能發生宕機可能的。zookeeper中leader的選舉過程爲:

集羣中的服務器會向其餘全部的Follower服務器發送消息,這個消息能夠形象化的稱之爲選票,選票主要由兩個信息組成,所推舉的Leader服務器的ID(即配置在myid文件中的數字),以及該服務器的事務ID,事務表示對服務器狀態變動的操做,一個服務器的事務ID越大,則其數據越新。整個過程以下所述:

  • 1.Follower服務器投出選票(SID,ZXID),第一次每一個Follower都會推選本身爲Leader服務器,也就是說每一個Follower第一次投出的選票是本身的服務器ID和事務ID。
  • 2.每一個Follower都會接收到來自於其餘Follower的選票,它會基於以下規則從新生成一張選票:比較收到的選票和本身的ZXID的大小,選取其中最大的;若ZXID同樣則選取SID即服務器ID最大的。最終每一個服務器都會從新生成一張選票,並將該選票投出去。

  這樣通過多輪投票後,若是某一臺服務器獲得了超過半數的選票,則其將當前選爲Leader。由以上分析可知,Zookeeper集羣對Leader服務器的選擇具備偏向性,偏向於那些ZXID更大,即數據更新的機器。

  整個過程以下圖所示:

    因此這裏實際上簡化了,有一個最後達成一致的細節過程須要進一步闡述(後續補充)。

故障恢復

Zookeeper經過事務日誌和數據快照來避免由於服務器故障致使的數據丟失。這一點上全部採用事務機制的存儲實現都同樣,採用WAL+重放機制實現。

  • 事務日誌是指服務器在更新內存數據前先將事務操做以日誌的方式寫入磁盤,Leader和Follower服務器都會記錄事務日誌。
  • 數據快照是指週期性經過深度遍歷的方式將內存中的樹形結構數據轉入外存快照中。但要注意這種快照是"模糊"的,由於可能在作快照時內存數據發生了變化。可是由於Zookeeper自己對事務操做進行了冪等性保證,故在將快照加載進內存後會經過執行事務日誌的方式來說數據恢復到最新狀態。

Zookeeper鏈接狀態管理

  zookeeper的鏈接狀態機以下:

   從上可知,共有5種主要狀態。實際上還有NOT_CONNECTED、CONNECTEDREADONLY、ASSOCIATING、RECONNECTED。

  https://curator.apache.org/apidocs/org/apache/curator/framework/state/ConnectionState.html

事務

未完待續。。。

客戶端緩存數據管理

未完待續。。。

權限體系

  關於zookeeper的acl認證機制,及相關集成,可參考zookeeper acl認證機制及dubbo、kafka集成、zooviewer/idea zk插件配置

分析zookeeper的事務日誌 

  可參見http://www.pianshen.com/article/6006190069/。

web監控工具

https://blog.imaginea.com/monitoring-zookeeper-with-exhibitor/

http://www.javashuo.com/article/p-qenrfpus-nc.html

https://github.com/soabase/exhibitor/wiki/Running-Exhibitor

http://www.javashuo.com/article/p-wlhbcppf-gz.html

zk etcd consul性能測試對比:https://coreos.com/blog/performance-of-etcd.html

相關文章
相關標籤/搜索