Zookeeper資料與儲存
一、前言
前面分析了Zookeeper對請求的處理,本篇博文接著分析Zookeeper中如何對底層資料進行儲存,資料儲存被分為記憶體資料儲存與磁碟資料儲存。
二、資料與儲存
2.1 記憶體資料
Zookeeper的資料模型是樹結構,在記憶體資料庫中,儲存了整棵樹的內容,包括所有的節點路徑、節點資料、ACL資訊,Zookeeper會定時將這個資料儲存到磁碟上(snapshot)。
1. DataTree
DataTree是記憶體資料儲存的核心,是一個樹結構,代表了記憶體中一份完整的資料。DataTree不包含任何與網路、客戶端連線及請求處理相關的業務邏輯,是一個獨立的元件。
2. DataNode
DataNode是資料儲存的最小單元,其內部除了儲存了結點的資料內容、ACL列表、節點狀態之外,還記錄了父節點的引用和子節點列表兩個屬性,其也提供了對子節點列表進行操作的介面。
3. ZKDatabase
Zookeeper的記憶體資料庫,管理Zookeeper的所有會話、DataTree儲存和事務日誌。ZKDatabase會定時向磁碟dump快照資料,同時在Zookeeper啟動時,會通過磁碟的事務日誌和快照檔案恢復成一個完整的記憶體資料庫。
2.2 事務日誌
1. 檔案儲存
在配置Zookeeper叢集時需要配置dataDir目錄,其用來儲存事務日誌檔案。也可以為事務日誌單獨分配一個檔案儲存目錄:dataLogDir。若配置dataLogDir為/home/admin/zkData/zk_log,那麼Zookeeper在執行過程中會在該目錄下建立一個名字為version-2的子目錄,該目錄確定了當前Zookeeper使用的事務日誌格式版本號,當下次某個Zookeeper版本對事務日誌格式進行變更時,此目錄也會變更,即在version-2子目錄下會生成一系列檔案大小一致(64MB)
2. 日誌格式
在配置好日誌檔案目錄,啟動Zookeeper後,完成如下操作
(1) 建立/test_log節點,初始值為v1。
(2) 更新/test_log節點的資料為v2。
(3) 建立/test_log/c節點,初始值為v1。
(4) 刪除/test_log/c節點。
經過四步操作後,會在/log/version-2/目錄下生成一個日誌檔案,筆者下是log.cec。
將Zookeeper下的zookeeper-3.4.6.jar和slf4j-api-1.6.1.jar複製到/log/version-2目錄下,使用如下命令開啟log.cec檔案。
java -classpath ./zookeeper-3.4.6.jar:./slf4j-api-1.6.1.jar org.apache.zookeeper.server.LogFormatter log.cec
ZooKeeper Transactional Log File with dbid 0 txnlog format version 2 。是檔案頭資訊,主要是事務日誌的DBID和日誌格式版本號。
...session 0x159...0xcec createSession 30000。表示客戶端會話建立操作。
...session 0x159...0xced create '/test_log,... 。表示建立/test_log節點,資料內容為#7631(v1)。
...session 0x159...0xcee setData ‘/test_log,...。表示設定了/test_log節點資料,內容為#7632(v2)。
...session 0x159...0xcef create ’/test_log/c,...。表示建立節點/test_log/c。
...session 0x159...0xcf0 delete '/test_log/c。表示刪除節點/test_log/c。
3. 日誌寫入
FileTxnLog負責維護事務日誌對外的介面,包括事務日誌的寫入和讀取等。Zookeeper的事務日誌寫入過程大體可以分為如下6個步驟。
(1) 確定是否有事務日誌可寫。當Zookeeper伺服器啟動完成需要進行第一次事務日誌的寫入,或是上一次事務日誌寫滿時,都會處於與事務日誌檔案斷開的狀態,即Zookeeper伺服器沒有和任意一個日誌檔案相關聯。因此在進行事務日誌寫入前,Zookeeper首先會判斷FileTxnLog元件是否已經關聯上一個可寫的事務日誌檔案。若沒有,則會使用該事務操作關聯的ZXID作為字尾建立一個事務日誌檔案,同時構建事務日誌的檔案頭資訊,並立即寫入這個事務日誌檔案中去,同時將該檔案的檔案流放入streamToFlush集合,該集合用來記錄當前需要強制進行資料落盤的檔案流。
(2) 確定事務日誌檔案是否需要擴容(預分配)。Zookeeper會採用磁碟空間預分配策略。當檢測到當前事務日誌檔案剩餘空間不足4096位元組時,就會開始進行檔案空間擴容,即在現有檔案大小上,將檔案增加65536KB(64MB),然後使用"0"填充被擴容的檔案空間。
(3) 事務序列化。對事務頭和事務體的序列化,其中事務體又可分為會話建立事務、節點建立事務、節點刪除事務、節點資料更新事務等。
(4) 生成Checksum。為保證日誌檔案的完整性和資料的準確性,Zookeeper在將事務日誌寫入檔案前,會計算生成Checksum。
(5) 寫入事務日誌檔案流。將序列化後的事務頭、事務體和Checksum寫入檔案流中,此時併為寫入到磁碟上。
(6) 事務日誌刷入磁碟。由於步驟5中的快取原因,無法實時地寫入磁碟檔案中,因此需要將快取資料強制刷入磁碟。
4. 日誌截斷
在Zookeeper執行過程中,可能出現非Leader記錄的事務ID比Leader上大,這是非法執行狀態。此時,需要保證所有機器必須與該Leader的資料保持同步,即Leader會發送TRUNC命令給該機器,要求進行日誌截斷,Learner收到該命令後,就會刪除所有包含或大於該事務ID的事務日誌檔案。
2.3 snapshot-資料快照
資料快照是Zookeeper資料儲存中非常核心的執行機制,資料快照用來記錄Zookeeper伺服器上某一時刻的全量記憶體資料內容,並將其寫入指定的磁碟檔案中。
1. 檔案儲存
與事務檔案類似,Zookeeper快照檔案也可以指定特定磁碟目錄,通過dataDir屬性來配置。若指定dataDir為/home/admin/zkData/zk_data,則在執行過程中會在該目錄下建立version-2的目錄,該目錄確定了當前Zookeeper使用的快照資料格式版本號。在Zookeeper執行時,會生成一系列檔案。
2. 資料快照
FileSnap負責維護快照資料對外的介面,包括快照資料的寫入和讀取等,將記憶體資料庫寫入快照資料檔案其實是一個序列化過程。針對客戶端的每一次事務操作,Zookeeper都會將他們記錄到事務日誌中,同時也會將資料變更應用到記憶體資料庫中,Zookeeper在進行若干次事務日誌記錄後,將記憶體資料庫的全量資料Dump到本地檔案中,這就是資料快照。其步驟如下
(1) 確定是否需要進行資料快照。每進行一次事務日誌記錄之後,Zookeeper都會檢測當前是否需要進行資料快照,考慮到資料快照對於Zookeeper機器的影響,需要儘量避免Zookeeper叢集中的所有機器在同一時刻進行資料快照。採用過半隨機策略進行資料快照操作。
(2) 切換事務日誌檔案。表示當前的事務日誌已經寫滿,需要重新建立一個新的事務日誌。
(3) 建立資料快照非同步執行緒。建立單獨的非同步執行緒來進行資料快照以避免影響Zookeeper主流程。
(4) 獲取全量資料和會話資訊。從ZKDatabase中獲取到DataTree和會話資訊。
(5) 生成快照資料檔名。Zookeeper根據當前已經提交的最大ZXID來生成資料快照檔名。
(6) 資料序列化。首先序列化檔案頭資訊,然後再對會話資訊和DataTree分別進行序列化,同時生成一個Checksum,一併寫入快照資料檔案中去。
2.4 初始化
在Zookeeper伺服器啟動期間,首先會進行資料初始化工作,用於將儲存在磁碟上的資料檔案載入到Zookeeper伺服器記憶體中。
1. 初始化流程
Zookeeper的初始化過程如下圖所示
資料的初始化工作是從磁碟上載入資料的過程,主要包括了從快照檔案中載入快照資料和根據實物日誌進行資料修正兩個過程。
(1) 初始化FileTxnSnapLog。FileTxnSnapLog是Zookeeper事務日誌和快照資料訪問層,用於銜接上層業務和底層資料儲存,底層資料包含了事務日誌和快照資料兩部分。FileTxnSnapLog中對應FileTxnLog和FileSnap。
(2) 初始化ZKDatabase。首先構建DataTree,同時將FileTxnSnapLog交付ZKDatabase,以便記憶體資料庫能夠對事務日誌和快照資料進行訪問。在ZKDatabase初始化時,DataTree也會進行相應的初始化工作,如建立一些預設結點,如/、/zookeeper、/zookeeper/quota三個節點。
(3) 建立PlayBackListener。其主要用來接收事務應用過程中的回撥,在Zookeeper資料恢復後期,會有事務修正過程,此過程會回撥PlayBackListener來進行對應的資料修正。
(4) 處理快照檔案。此時可以從磁碟中恢復資料了,首先從快照檔案開始載入。
(5) 獲取最新的100個快照檔案。更新時間最晚的快照檔案包含了最新的全量資料。
(6) 解析快照檔案。逐個解析快照檔案,此時需要進行反序列化,生成DataTree和sessionsWithTimeouts,同時還會校驗Checksum及快照檔案的正確性。對於100個快找檔案,如果正確性校驗通過時,通常只會解析最新的那個快照檔案。只有最新快照檔案不可用時,才會逐個進行解析,直至100個快照檔案全部解析完。若將100個快照檔案解析完後還是無法成功恢復一個完整的DataTree和sessionWithTimeouts,此時伺服器啟動失敗。
(7) 獲取最新的ZXID。此時根據快照檔案的檔名即可解析出最新的ZXID:zxid_for_snap。該ZXID代表了Zookeeper開始進行資料快照的時刻。
(8) 處理事務日誌。此時伺服器記憶體中已經有了一份近似全量的資料,現在開始通過事務日誌來更新增量資料。
(9) 獲取所有zxid_for_snap之後提交的事務。此時,已經可以獲取快照資料的最新ZXID。只需要從事務日誌中獲取所有ZXID比步驟7得到的ZXID大的事務操作。
(10) 事務應用。獲取大於zxid_for_snap的事務後,將其逐個應用到之前基於快照資料檔案恢復出來的DataTree和sessionsWithTimeouts。每當有一個事務被應用到記憶體資料庫中後,Zookeeper同時會回撥PlayBackListener,將這事務操作記錄轉換成Proposal,並儲存到ZKDatabase的committedLog中,以便Follower進行快速同步。
(11) 獲取最新的ZXID。待所有的事務都被完整地應用到記憶體資料庫中後,也就基本上完成了資料的初始化過程,此時再次獲取ZXID,用來標識上次伺服器正常執行時提交的最大事務ID。
(12) 校驗epoch。epoch標識了當前Leader週期,叢集機器相互通訊時,會帶上這個epoch以確保彼此在同一個Leader週期中。完成資料載入後,Zookeeper會從步驟11中確定ZXID中解析出事務處理的Leader週期:epochOfZxid。同時也會從磁碟的currentEpoch和acceptedEpoch檔案中讀取上次記錄的最新的epoch值,進行校驗。
2.5 資料同步
整個叢集完成Leader選舉後,Learner會向Leader進行註冊,當Learner向Leader完成註冊後,就進入資料同步環節,同步過程就是Leader將那些沒有在Learner伺服器上提交過的事務請求同步給Learner伺服器,大體過程如下
(1) 獲取Learner狀態。在註冊Learner的最後階段,Learner伺服器會發送給Leader伺服器一個ACKEPOCH資料包,Leader會從這個資料包中解析出該Learner的currentEpoch和lastZxid。
(2) 資料同步初始化。首先從Zookeeper記憶體資料庫中提取出事務請求對應的提議快取佇列proposals,同時完成peerLastZxid(該Learner最後處理的ZXID)、minCommittedLog(Leader提議快取佇列commitedLog中最小的ZXID)、maxCommittedLog(Leader提議快取佇列commitedLog中的最大ZXID)三個ZXID值的初始化。
對於叢集資料同步而言,通常分為四類,直接差異化同步(DIFF同步)、先回滾再差異化同步(TRUNC+DIFF同步)、僅回滾同步(TRUNC同步)、全量同步(SNAP同步),在初始化階段,Leader會優先以全量同步方式來同步資料。同時,會根據Leader和Learner之間的資料差異情況來決定最終的資料同步方式。
· 直接差異化同步(DIFF同步,peerLastZxid介於minCommittedLog和maxCommittedLog之間)。Leader首先向這個Learner傳送一個DIFF指令,用於通知Learner進入差異化資料同步階段,Leader即將把一些Proposal同步給自己,針對每個Proposal,Leader都會通過傳送PROPOSAL內容資料包和COMMIT指令資料包來完成,
· 先回滾再差異化同步(TRUNC+DIFF同步,Leader已經將事務記錄到本地事務日誌中,但是沒有成功發起Proposal流程)。當Leader發現某個Learner包含了一條自己沒有的事務記錄,那麼就需要該Learner進行事務回滾,回滾到Leader伺服器上存在的,同時也是最接近於peerLastZxid的ZXID。
· 僅回滾同步(TRUNC同步,peerLastZxid大於maxCommittedLog)。Leader要求Learner回滾到ZXID值為maxCommittedLog對應的事務操作。
· 全量同步(SNAP同步,peerLastZxid小於minCommittedLog或peerLastZxid不等於lastProcessedZxid)。Leader無法直接使用提議快取佇列和Learner進行同步,因此只能進行全量同步。Leader將本機的全量記憶體資料同步給Learner。Leader首先向Learner傳送一個SNAP指令,通知Learner即將進行全量同步,隨後,Leader會從記憶體資料庫中獲取到全量的資料節點和會話超時時間記錄器,將他們序列化後傳輸給Learner。Learner接收到該全量資料後,會對其反序列化後載入到記憶體資料庫中。
三、總結
本篇博文主要講解了Zookeeper的資料與儲存,包括記憶體資料,快照資料,以及如何進行資料的同步等細節,至此,Zookeeper的理論學習部分已經全部完成,之後會進行原始碼分析,也謝謝各位園友的觀看~