一、內(nèi)存數(shù)據(jù)
Zookeeper的數(shù)據(jù)模型是樹結(jié)構(gòu),在內(nèi)存數(shù)據(jù)庫中,存儲了整棵樹的內(nèi)容,包括所有的節(jié)點路徑、節(jié)點數(shù)據(jù)、ACL信息,Zookeeper會定時將這個數(shù)據(jù)存儲到磁盤上。
1、DataTree
DataTree是內(nèi)存數(shù)據(jù)存儲的核心,是一個樹結(jié)構(gòu),代表了內(nèi)存中一份完整的數(shù)據(jù)。DataTree不包含任何與網(wǎng)絡(luò)、客戶端連接及請求處理相關(guān)的業(yè)務(wù)邏輯,是一個獨立的組件。
2、DataNode
DataNode是數(shù)據(jù)存儲的最小單元,其內(nèi)部除了保存了結(jié)點的數(shù)據(jù)內(nèi)容、ACL列表、節(jié)點狀態(tài)之外,還記錄了父節(jié)點的引用和子節(jié)點列表兩個屬性,其也提供了對子節(jié)點列表進行操作的接口。
3、ZKDatabase
Zookeeper的內(nèi)存數(shù)據(jù)庫,管理Zookeeper的所有會話、DataTree存儲和事務(wù)日志。ZKDatabase會定時向磁盤dump快照數(shù)據(jù),同時在Zookeeper啟動時,會通過磁盤的事務(wù)日志和快照文件恢復(fù)成一個完整的內(nèi)存數(shù)據(jù)庫。
二、事務(wù)日志
1、文件存儲
在配置Zookeeper集群時需要配置dataDir目錄,其用來存儲事務(wù)日志文件。也可以為事務(wù)日志單獨分配一個文件存儲目錄:dataLogDir。若配置dataLogDir為/home/admin/zkData/zk_log,那么Zookeeper在運行過程中會在該目錄下建立一個名字為version-2的子目錄,該目錄確定了當(dāng)前Zookeeper使用的事務(wù)日志格式版本號,當(dāng)下次某個Zookeeper版本對事務(wù)日志格式進行變更時,此目錄也會變更,即在version-2子目錄下會生成一系列文件大小一致(64MB)的文件。
2、日志格式
在配置好日志文件目錄,啟動Zookeeper后,完成如下操作
(1) 創(chuàng)建/test_log節(jié)點,初始值為v1。
(2) 更新/test_log節(jié)點的數(shù)據(jù)為v2。
(3) 創(chuàng)建/test_log/c節(jié)點,初始值為v1。
(4) 刪除/test_log/c節(jié)點。
經(jīng)過四步操作后,會在/log/version-2/目錄下生成一個日志文件,筆者下是log.cec。
將Zookeeper下的zookeeper-3.4.6.jar和slf4j-api-1.6.1.jar復(fù)制到/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

(1) ZooKeeper Transactional Log File with dbid 0 txnlog format version 2 :是文件頭信息,主要是事務(wù)日志的DBID和日志格式版本。
(2) ...session 0x159...0xcec createSession 30000:表示客戶端會話創(chuàng)建操作。
(3) ...session 0x159...0xced create '/test_log,... :表示創(chuàng)建/test_log節(jié)點,數(shù)據(jù)內(nèi)容為#7631(v1)。
(4) ...session 0x159...0xcee setData ‘/test_log,...:表示設(shè)置了/test_log節(jié)點數(shù)據(jù),內(nèi)容為#7632(v2)。
(5) ...session 0x159...0xcef create ’/test_log/c,...:表示創(chuàng)建節(jié)點/test_log/c。
(6) ...session 0x159...0xcf0 delete '/test_log/c:表示刪除節(jié)點/test_log/c。
3、日志寫入
FileTxnLog負(fù)責(zé)維護事務(wù)日志對外的接口,包括事務(wù)日志的寫入和讀取等。Zookeeper的事務(wù)日志寫入過程大體可以分為如下6個步驟。
(1) 確定是否有事務(wù)日志可寫:當(dāng)Zookeeper服務(wù)器啟動完成需要進行第一次事務(wù)日志的寫入,或是上一次事務(wù)日志寫滿時,都會處于與事務(wù)日志文件斷開的狀態(tài),即Zookeeper服務(wù)器沒有和任意一個日志文件相關(guān)聯(lián)。因此在進行事務(wù)日志寫入前,Zookeeper首先會判斷FileTxnLog組件是否已經(jīng)關(guān)聯(lián)上一個可寫的事務(wù)日志文件。若沒有,則會使用該事務(wù)操作關(guān)聯(lián)的ZXID作為后綴創(chuàng)建一個事務(wù)日志文件,同時構(gòu)建事務(wù)日志的文件頭信息,并立即寫入這個事務(wù)日志文件中去,同時將該文件的文件流放入streamToFlush集合,該集合用來記錄當(dāng)前需要強制進行數(shù)據(jù)落盤的文件流。
(2) 確定事務(wù)日志文件是否需要擴容(預(yù)分配):Zookeeper會采用磁盤空間預(yù)分配策略。當(dāng)檢測到當(dāng)前事務(wù)日志文件剩余空間不足4096字節(jié)時,就會開始進行文件空間擴容,即在現(xiàn)有文件大小上,將文件增加65536KB(64MB),然后使用"0"填充被擴容的文件空間。
(3) 事務(wù)序列化:對事務(wù)頭和事務(wù)體的序列化,其中事務(wù)體又可分為會話創(chuàng)建事務(wù)、節(jié)點創(chuàng)建事務(wù)、節(jié)點刪除事務(wù)、節(jié)點數(shù)據(jù)更新事務(wù)等。
(4) 生成Checksum:為保證日志文件的完整性和數(shù)據(jù)的準(zhǔn)確性,Zookeeper在將事務(wù)日志寫入文件前,會計算生成Checksum。
(5) 寫入事務(wù)日志文件流:將序列化后的事務(wù)頭、事務(wù)體和Checksum寫入文件流中,此時并為寫入到磁盤上。
(6) 事務(wù)日志刷入磁盤:由于步驟5中的緩存原因,無法實時地寫入磁盤文件中,因此需要將緩存數(shù)據(jù)強制刷入磁盤。
4、日志截斷
在Zookeeper運行過程中,可能出現(xiàn)非Leader記錄的事務(wù)ID比Leader上大,這是非法運行狀態(tài)。此時,需要保證所有機器必須與該Leader的數(shù)據(jù)保持同步,即Leader會發(fā)送TRUNC命令給該機器,要求進行日志截斷,Learner收到該命令后,就會刪除所有包含或大于該事務(wù)ID的事務(wù)日志文件。
三、snapshot-數(shù)據(jù)快照
數(shù)據(jù)快照是Zookeeper數(shù)據(jù)存儲中非常核心的運行機制,數(shù)據(jù)快照用來記錄Zookeeper服務(wù)器上某一時刻的全量內(nèi)存數(shù)據(jù)內(nèi)容,并將其寫入指定的磁盤文件中。
1、 文件存儲
與事務(wù)文件類似,Zookeeper快照文件也可以指定特定磁盤目錄,通過dataDir屬性來配置。若指定dataDir為/home/admin/zkData/zk_data,則在運行過程中會在該目錄下創(chuàng)建version-2的目錄,該目錄確定了當(dāng)前Zookeeper使用的快照數(shù)據(jù)格式版本號。在Zookeeper運行時,會生成一系列文件。
2、數(shù)據(jù)快照
FileSnap負(fù)責(zé)維護快照數(shù)據(jù)對外的接口,包括快照數(shù)據(jù)的寫入和讀取等,將內(nèi)存數(shù)據(jù)庫寫入快照數(shù)據(jù)文件其實是一個序列化過程。針對客戶端的每一次事務(wù)操作,Zookeeper都會將他們記錄到事務(wù)日志中,同時也會將數(shù)據(jù)變更應(yīng)用到內(nèi)存數(shù)據(jù)庫中,Zookeeper在進行若干次事務(wù)日志記錄后,將內(nèi)存數(shù)據(jù)庫的全量數(shù)據(jù)Dump到本地文件中,這就是數(shù)據(jù)快照。其步驟如下
(1) 確定是否需要進行數(shù)據(jù)快照:每進行一次事務(wù)日志記錄之后,Zookeeper都會檢測當(dāng)前是否需要進行數(shù)據(jù)快照,考慮到數(shù)據(jù)快照對于Zookeeper機器的影響,需要盡量避免Zookeeper集群中的所有機器在同一時刻進行數(shù)據(jù)快照。采用過半隨機策略進行數(shù)據(jù)快照操作。
(2) 切換事務(wù)日志文件:表示當(dāng)前的事務(wù)日志已經(jīng)寫滿,需要重新創(chuàng)建一個新的事務(wù)日志。
(3) 創(chuàng)建數(shù)據(jù)快照異步線程:創(chuàng)建單獨的異步線程來進行數(shù)據(jù)快照以避免影響Zookeeper主流程。
(4) 獲取全量數(shù)據(jù)和會話信息:從ZKDatabase中獲取到DataTree和會話信息。
(5) 生成快照數(shù)據(jù)文件名:Zookeeper根據(jù)當(dāng)前已經(jīng)提交的最大ZXID來生成數(shù)據(jù)快照文件名。
(6) 數(shù)據(jù)序列化:首先序列化文件頭信息,然后再對會話信息和DataTree分別進行序列化,同時生成一個Checksum,一并寫入快照數(shù)據(jù)文件中去。