本文做者:HelloGitHub-老荀git
Hi,這裏是 HelloGitHub 推出的 HelloZooKeeper 系列,免費開源、有趣、入門級的 ZooKeeper 教程,面向有編程基礎的新手。github
前一篇文章咱們介紹了 ZK 是如何進行選舉的,這篇咱們開始學習 ZK 是如何將數據持久化到磁盤中的。session
咱們經過以前的文章有介紹過,小S(Sync) 負責對辦事處的數據進行歸檔,因此今天他就是咱們的主角,讓咱們一塊兒深刻了解他的平常工做吧架構
爲了喚醒你們的遠古記憶,我放一張以前的圖片學習
今天咱們會重點講一下圖中的藍色部分,不過在此以前仍是得先從總體架構上介紹下 ZK 的數據管理,ZK 的數據大體是分爲了兩部分,一個是內存,一個就是磁盤文件。線程
雖然今天咱們的主角是磁盤文件,可是內存仍是稍微再提一下下,幫助你們記憶的同時也能有一個比較全面的視角去認知 ZK 總體的數據管理。設計
ZK 在內存中的存儲就是以前故事中有提到的兩個帳本:小紅本和小黃本。若是排除做爲回調通知記錄的小黃本,那 ZK 的內存中就是小紅本對應的哈希表而已,可是小黃本中的數據依然很是重要,因此須要將二者做爲總體一塊兒看待,以及以前我有說過 小F(Final) 掌管了這兩個帳本,做爲業務處理的最後一個負責人,小S(Sync) 從時間上來講是優先於 小F(Final) 先處理的,因此 ZK 的設計是優先將數據存入磁盤,再去修改內存中的數據保證儘量的提高數據的可靠性。下面咱們繼續瞭解磁盤文件(還真就提一下下!)3d
ZK 的開發者給 ZK 設計了兩種磁盤文件,對應的路徑分別是 zoo.cfg
配置中的 dataDir
和 dataLogDir
這兩項目錄的配置。爲了以後的描述清楚,我給這兩種磁盤文件起了名字: dataDir
對應 snapshot,dataLogDir
對應 log,log 就是的是 小S(Sync) 工做中的歸檔,snapshot 就是的是 小S(Sync) 工做中的快照。code
log 是負責順序記錄每個寫請求到文件,snapshot 則是直接將整個內存對象持久化至文件中。假設我如今 zoo.cfg
的配置是這樣:
dataDir=/tmp/zookeeper/snapshot dataLogDir=/tmp/zookeeper/log
當 ZK 啓動後會基於上面兩個路徑繼續建立 version-2
子路徑,以後的文件都會在該子路徑下建立
/tmp └── zookeeper ├── snapshot └── version-2 └── ... └── log └── version-2 └── ...
兩種文件分別是在何時被寫入磁盤的呢?寫入的內容又是哪些呢?咱們接下來對兩種文件一一進行分析。
log 文件名的格式是這樣 log.{zxid}
zxid 對應當時建立該文件時的最大 zxid,假設如今建立時 zxid 爲 0,那目錄結構會是這樣:
/tmp └── zookeeper └── log └── version-2 └── log.0
這個 log.0
文件建立的時機你也能夠簡單的理解爲當服務端收到第一個寫請求的時候,並且當建立完成後,並不能直接將數據寫入,而是要先寫一些文件頭的字段,好比大名鼎鼎的魔數,版本號等元信息。
而 log 文件的魔數是 ZKLG(4 個字節),版本號固定爲 2(4 個字節),還要記錄一個 dbId 固定爲 0(8 個字節) (當前沒用,可能以後會派用處吧),因此前 16 個字節是固定這樣的:
Z K L G 2 0 5A4B4C47 00000002 00000000 00000000
那以後的業務數據是如何記錄的呢?
每個寫請求均可以分爲四個部分:校驗和、請求頭、請求數據、簽名,校驗和是經過後面三個字段計算出來的,小S 每次收到寫請求後都會按照這樣的順序將對應請求的四個字段寫入 log 文件,因爲不一樣的業務請求數據不固定,並且數據長度也比較大,這裏就不給你們展現具體的值(若是你們想要知道這硬核的存儲過程,不妨給我留言,我之後單獨作下,嘗試逐個字節解釋)
而後是 zookeeper.txnLogSizeLimitInKb
這個環境變量配置,默認是 -1,這個配置限制了 log 單個文件大小(單位是 KB),每次 小S(Sync) 歸檔的時候(圖中右下角粉色部分「是否歸檔」),將數據統一刷到磁盤後,若是用戶手動配置了該參數,就會檢查當前 log 文件大小是否超過了該參數大小,若是超過了就會進行 rollLog,至關於下一次的寫請求會建立一個新的 log 文件。除此以外,當 小S(Sync) 每次快照的時候會強制執行一次 rollLog。
snapshot 文件名的格式是這樣 snapshot.{zxid}
zxid 對應當是建立該文件時的最大 zxid,假設如今建立是最大 zxid 是 0,那目錄結構會是這樣:
/tmp └── zookeeper └── snapshot └── version-2 └── snapshot.0
而關因而否快照(圖中中間區域粉色部分「是否快照」),以前有簡單介紹過是和隨機數有關,此次咱們深刻了解下。
首先有兩個配置 zookeeper.snapCount
(默認 100000)和 zookeeper.snapSizeLimitInKb
(默認 4194304 單位是KB,至關於 4 GB)在啓動後會基於這兩個配置分別生成兩個隨機數,假設上述的配置是按照默認的設置,這兩個隨機數的範圍就是:
randRoll = [0, 50000] randSize = [0, 4194304 * 1024 / 2]
能夠簡單的認爲就是上述兩個配置的一半以內的隨機數,至於 randSize
爲何要乘以 1024 由於最終文件計算大小是以 byte 做爲單位的。
而是否快照就是取決於上面兩個隨機數,有兩個條件:
zookeeper.snapCount
的一半並加上 randRoll
的數量zookeeper.snapSizeLimitInKb
的一半並加上 randSize
的大小上述條件知足任意一個條件後就會重置上面的兩個隨機數,並開始生成快照,生成快照這個過程是啓動一個子線程去建立的。
snapshot 和 log 還有個不一樣的地方就是,snapshot 文件 ZK 提供了三種不一樣的壓縮實現,GZIP、SNAPPY、CHECKED,經過 zookeeper.snapshot.compression.method
進行配置,默認是 CHECKED,就是原始按照字節順序寫入,另外兩個這裏就不展開了。那咱們接下來看看 snapshot 文件是怎麼記的吧。
和 log 文件同樣,也要先記一些文件的頭部字段,而 snapshot 文件的魔數是 ZKSN(4 個字節),版本號固定爲 2(4 個字節),還要記錄一個 dbId 固定爲 -1(8 個字節) (當前沒用,可能以後會派用處吧),因此前 16 個字節是固定這樣的:
Z K S N 2 -1 5A4B534E 00000002 FFFFFFFF FFFFFFFF
而後緊跟其後的部分客戶端的會話信息,客戶端的數量,而後循環記錄每個客戶端的 sessionId、超時時間,而後是小紅本里的全部信息了包括但不限於 ACL,節點的統計數據,節點的數據,子節點的信息等。最後一部分就是校驗和和簽名。和 log 同樣,若是你們有興趣的話,我以後單獨再作一篇逐個字節講解的。
若是隻是單單存文件,那這文件也沒什麼用,因此文件另外一個重要用途就是幫助 ZK 恢復服務端的信息。
在 ZK 啓動的時候就會嘗試讀取 dataDir
和 dataLogDir
這兩個目錄下的文件,假設在這兩個路徑下的文件是:
/tmp └── zookeeper ├── snapshot └── version-2 └── snapshot.5 └── snapshot.37 └── snapshot.100 └── log └── version-2 └── log.0 └── log.6 └── log.38 └── log.90 └── log.108
我這裏例子中的文件名的後綴數字是我隨便舉例只是爲了說明恢復的過程,實際未必是這樣,切記。
如今 ZK 服務端啓動後,會先從 snapshot 的目錄中找到 zxid 最大的那個文件,而後根據它的內容恢復 小紅本
恢復完後就會去 log 文件目錄下尋找全部比 100 要大的 log 文件以及比 100 要略小一點的 log 文件,本例子中就是 log.90
和 log.108
這兩個文件。
你可能會問爲何要找小於 100 的 log.90
這個文件呢?由於文件名中的 90 只是說明這個文件創建的時候,最大的 zxid 是 90,可是文件中記錄的寫請求是頗有可能會大於 100 的,因此 log.90
也須要被找到。
而後就是從 log.90
這個文件開始恢復,先從 zxid 比 100 大的寫請求開始讀取並執行該寫請求,而後繼續讀取 log.108
,等待全部符合條件的 log 文件讀取後,整個 ZK 的數據就恢復完成了。
今天咱們介紹了關於 ZK 持久化的知識:
今天的內容仍是比較簡單的,爲咱們下一篇文章打好了基礎~下一篇咱們開始介紹以前選舉中沒有介紹的內容:選舉完成後,Follower 和 Observer 是如何同 Leader 同步數據的?
老規矩,若是你有任何對文章中的疑問也能夠是建議或者是對 ZK 原理部分的疑問,歡迎來倉庫中提 issue 給咱們,或者來語雀話題討論。