下述各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是最普遍使用的分佈式協調服務,包括dubbo、kafka、hadoop、es-job等都依賴於zookeeper提供的分佈式協調和註冊服務。其餘用於提供註冊服務的中間件還包括consul以及etcd、eureka,但都不及zookeeper普遍。其餘適用場景可見https://xie.infoq.cn/article/1dcd3f8b100645e0da782a279。node
官網: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中,節點分爲下列幾種角色:github
在一個zookeeper集羣,各節點之間的交互以下所示:web
注:幾乎全部現代基於分佈式架構的中間件都是採用相似作法,例如kafka、es等。redis
從上可知,全部請求均由客戶端發起,它多是本地zkCli或java客戶端。 各角色詳細職責以下。算法
leader的職責包括:sql
follower的主要職責爲:
向Leader發送請求;
接收Leader的消息並進行處理;
接收Zookeeper Client的請求,若是爲寫清求,轉發給Leader進行處理
Follower的工做流程簡圖以下所示,在實際實現中,Follower是經過5個線程來實現功能的。
各類消息的含義以下:
PING:心跳消息。 PROPOSAL:Leader發起的提案,要求Follower投票。 COMMIT:服務器端最新一次提案的信息。 UPTODATE:代表同步完成。 REVALIDATE:根據Leader的REVALIDATE結果,關閉待revalidate的session仍是容許其接受消息。 SYNC:返回SYNC結果到客戶端,這個消息最初由客戶端發起,用來強制獲得最新的更新。
雖然zookeeper採用的是文件系統存儲機制,可是全部數據數據都存儲於內存中。其對外提供的視圖相似於Unix文件系統。樹的根Znode節點至關於Unix文件系統的根路徑。
zk中的節點稱之爲znode(也叫data register,也就是存儲數據的文件夾),按其生命週期的長短能夠分爲持久結點(PERSISTENT)和臨時結點(EPHEMERAL);在建立時還可選擇是否由Zookeeper服務端在其路徑後添加一串序號用來區分同一個父結點下多個結點建立的前後順序。
通過組合就有如下4種Znode結點類型:
zookeeper經過下列機制實現一致性保證:
» 全部更新請求順序進行,來自同一個client的更新請求按其發送順序依次執行 » 數據更新原子性,一次數據更新要麼成功,要麼失敗 » 全局惟一數據視圖,client不管鏈接到哪一個server,數據視圖都是一致的,基於全部寫請求所有由leader完成,而後同步實現 » 實時性,在必定事件範圍內,client能讀到最新數據
» Zookeeper是一個由多個server組成的集羣 » 一個leader,多個follower » 每一個server保存一份數據副本 » 全局數據一致 » 分佈式讀寫 » 更新請求所有轉發由leader完成,並在成功後同步給follower
客戶端寫請求的過程以下:
其過程爲:
因爲每次的請求都須要轉發給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個。
znode節點的狀態信息中包含czxid, 那麼什麼是zxid呢? 在zk中,狀態的每一次改變, 都對應着一個遞增的Transaction id, 該id稱爲zxid. 因爲zxid的遞增性質, 若是zxid1小於zxid2, 那麼zxid1確定先於zxid2發生. 建立任意節點, 或者更新任意節點的數據, 或者刪除任意節點, 都會致使Zookeeper狀態發生改變, 從而致使zxid的值增長.
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),該限制爲任何節點數據字段的最大可存儲字節數,同時也限制了任何父節點能夠擁有的子節點數。
paxos算法基於這樣的原理:
• 在一個分佈式數據庫系統中,若是各節點的初始狀態一致,每一個節點都執行相同的操做序列,那麼他們最後能獲得一個一致的狀態。 • Paxos算法解決的什麼問題呢,解決的就是保證每一個節點執行相同的操做序列。好吧,這還不簡單,master維護一個 全局寫隊列,全部寫操做都必須 放入這個隊列編號,那麼不管咱們寫多少個節點,只要寫操做是按編號來的,就能保證一 致性。沒錯,就是這樣,但是若是master掛了呢。 • Paxos算法經過投票來對寫操做進行全局編號,同一時刻,只有一個寫操做被批准,同時併發的寫操做要去爭取選票, 只有得到過半數選票的寫操做纔會被 批准(因此永遠只會有一個寫操做獲得批准),其餘的寫操做競爭失敗只好再發起一 輪投票,就這樣,在日復一日年復一年的投票中,全部寫操做都被嚴格編號排 序。編號嚴格遞增,當一個節點接受了一個 編號爲100的寫操做,以後又接受到編號爲99的寫操做(由於網絡延遲等不少不可預見緣由),它立刻能意識到本身 數據 不一致了,自動中止對外服務並重啓同步過程。任何一個節點掛掉都不會影響整個集羣的數據一致性(總2n+1臺,除非掛掉大於n臺)
所以在生產中,要求zookeeper部署3(單機房)或5(單機房或多機房)或7(跨機房)個節點的集羣。
因爲zookeeper的java官方客戶端太不友好,所以實際中通常使用三方客戶端Curator。故不對zookeeper客戶端進行詳細分析,參見本文首部對curator的詳解。
watch是zookeeper針對節點的一次性觀察者機制(即一次觸發後就失效,須要手工從新建立watch),行爲上相似於數據庫的觸發器。
當watch監視的數據發生時,通知設置了該watch的client,客戶端即watcher。watcher的機制是監聽數據發生了某些變化,因此必定會有對應的事件類型和狀態類型,一個客戶端能夠監控多個節點,在代碼中體如今new了幾個就產生幾個watcher,只要節點變化都會執行一遍process。其示意圖以下:
在zookeeper中,watch是採用推送機制實現的,而不是客戶端輪訓(有些中間件採用拉的模式,例如kafka消費者,這主要取決於設計者認爲的合理性,通常來講流量很大的適合於拉的模式,這樣更好作控制,不然客戶端容易失控;反之推的模式)。watch有兩種類型的事件可以監聽:znode相關的及客戶端實例相關的。分別爲:
總結起來,zk watch的特性爲:
客戶端設置的每一個監視點與會話關聯,若是會話過時,等待中的監視點將會被刪除。不過監視點能夠跨越不一樣服務端的鏈接而保持,例如,當一個ZooKeeper客戶端與一個ZooKeeper服務端的鏈接斷開後鏈接到集合中的另外一個服務端,客戶端會發送未觸發的監視點列表,在註冊監視點時,服務端將要檢查已監視的znode節點在以前註冊監視點以後是否已經變化,若是znode節點已經發生變化,一個監視點的事件就會被髮送給客戶端,不然在新的服務端上註冊監視點。這一機制使得咱們能夠關心邏輯層會話,而非底層鏈接自己。
兩種狀況下會發生Leader節點的選舉,集羣初始構建的時候;其次,不管如何,leader老是有可能發生宕機可能的。zookeeper中leader的選舉過程爲:
集羣中的服務器會向其餘全部的Follower服務器發送消息,這個消息能夠形象化的稱之爲選票,選票主要由兩個信息組成,所推舉的Leader服務器的ID(即配置在myid文件中的數字),以及該服務器的事務ID,事務表示對服務器狀態變動的操做,一個服務器的事務ID越大,則其數據越新。整個過程以下所述:
這樣通過多輪投票後,若是某一臺服務器獲得了超過半數的選票,則其將當前選爲Leader。由以上分析可知,Zookeeper集羣對Leader服務器的選擇具備偏向性,偏向於那些ZXID更大,即數據更新的機器。
整個過程以下圖所示:
因此這裏實際上簡化了,有一個最後達成一致的細節過程須要進一步闡述(後續補充)。
Zookeeper經過事務日誌和數據快照來避免由於服務器故障致使的數據丟失。這一點上全部採用事務機制的存儲實現都同樣,採用WAL+重放機制實現。
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/。
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