Apache ZooKeeper - ZK的数据和文件
文章目錄
- 目標
- 內存數據
- 事務日志
- 數據快照
- 小結
目標
本篇博文,我們主要聚焦在ZooKeeper 程序運行期間,都會處理哪些數據,以及他們的存儲格式和存儲位置。
ZooKeeper 服務提供了創建節點、添加 Watcher 監控機制、集群服務等豐富的功能。這些功能服務的實現,離不開底層數據的支持。
從數據存儲地點角度講,ZooKeeper 服務產生的數據可以分為內存數據和磁盤數據。
而從數據的種類和作用上來說,又可以分為事務日志數據和數據快照數據。
內存數據
ZooKeeper 的數據模型可以看作一棵樹形結構,而數據節點就是這棵樹上的葉子節點。
從數據存儲的角度看,ZooKeeper 的數據模型是存儲在內存中的。
可以把 ZooKeeper 的數據模型看作是存儲在內存中的數據庫,而這個數據庫不但存儲數據的節點信息,還存儲每個數據節點的 ACL 權限信息以及 stat 狀態信息等。
源碼中,ZooKeeper 數據模型是通過 DataTree 類來定義的。
如下面的代碼所示,DataTree 類定義了一個 ZooKeeper 數據的內存結構。DataTree 的內部定義類 nodes 節點類型、root 根節點信息、子節點的 WatchManager 監控信息等數據模型中的相關信息。可以說,一個 DataTree 類定義了 ZooKeeper 內存數據的邏輯結構。
public class DataTree {private DataNode rootprivate final WatchManager dataWatchesprivate final WatchManager childWatchesprivate static final String rootZookeeper = "/";}事務日志
我們知道為了整個 ZooKeeper 集群中數據的一致性,Leader 服務器會向 ZooKeeper 集群中的其他角色服務發送數據同步信息,在接收到數據同步信息后, ZooKeeper 集群中的 Follow 和 Observer 服務器就會進行數據同步。
而這兩種角色服務器所接收到的信息就是 Leader 服務器的事務日志。在接收到事務日志后,并在本地服務器上執行。這種數據同步的方式,避免了直接使用實際的業務數據,減少了網絡傳輸的開銷,提升了整個 ZooKeeper 集群的執行性能。
在我們啟動一個 ZooKeeper 服務器之前,首先要創建一個 zoo.cfg 文件并進行相關配置,其中有一項配置就是 dataLogDir 。在這項配置中,我們會指定該臺 ZooKeeper 服務器事務日志的存放位置。
在 ZooKeeper 服務的底層實現中,是通過 FileTxnLog 類來實現事務日志的底層操作的。如下圖代碼所示,在 FileTxnLog 類中定義了一些屬性字段,分別是:
-
preAllocSize:可存儲的日志文件大小。如用戶不進行特殊設置,默認的大小為 65536*1024 字節。
-
TXNLOG_MAGIC:設置日志文件的魔數信息為ZKLG。
-
VERSION:設置日志文件的版本信息。
-
lastZxidSeen:最后一次更新日志得到的 ZXID。
定義了事務日志操作的相關指標參數后,在 FileTxnLog 類中調用 static 靜態代碼塊,來將這些配置參數進行初始化。比如讀取 preAllocSize 參數分配給日志文件的空間大小等操作。
static {LOG = LoggerFactory.getLogger(FileTxnLog.class);String size = System.getProperty("zookeeper.preAllocSize");if (size != null) {try {preAllocSize = Long.parseLong(size) * 1024;} catch (NumberFormatException e) {LOG.warn(size + " is not a valid value for preAllocSize");}}Long fsyncWarningThreshold;if ((fsyncWarningThreshold = Long.getLong("zookeeper.fsync.warningthresholdms")) == null)fsyncWarningThreshold = Long.getLong("fsync.warningthresholdms", 1000);fsyncWarningThresholdMS = fsyncWarningThreshold;經過參數定義和日志文件的初始化創建后,在 ZooKeeper 服務器的 dataDir 路徑下就生成了一個用于存儲事務性操作的日志文件。我們知道在 ZooKeeper 服務運行過程中,會不斷地接收和處理來自客戶端的事務性會話請求,這就要求每次在處理事務性請求的時候,都要記錄這些信息到事務日志中。
如下面的代碼所示,在 FileTxnLog 類中,實現記錄事務操作的核心方法是 append。從方法的命名中可以看出,ZooKeeper 采用末尾追加的方式來維護新的事務日志數據到日志文件中。append 方法首先會解析事務請求的頭信息,并根據解析出來的 zxid 字段作為事務日志的文件名,之后設置日志的文件頭信息 magic、version、dbid 以及日志文件的大小 。
public synchronized boolean append(TxnHeader hdr, Record txn)throws IOException{if (hdr == null) {return false;}if (hdr.getZxid() <= lastZxidSeen) {LOG.warn("Current zxid " + hdr.getZxid()+ " is <= " + lastZxidSeen + " for "+ hdr.getType());} else {lastZxidSeen = hdr.getZxid();}if (logStream==null) {if(LOG.isInfoEnabled()){LOG.info("Creating new log file: log." +Long.toHexString(hdr.getZxid()));}logFileWrite = new File(logDir, ("log." +Long.toHexString(hdr.getZxid())));fos = new FileOutputStream(logFileWrite);logStream=new BufferedOutputStream(fos);oa = BinaryOutputArchive.getArchive(logStream);FileHeader fhdr = new FileHeader(TXNLOG_MAGIC,VERSION, dbId);fhdr.serialize(oa, "fileheader");// Make sure that the magic number is written before padding.logStream.flush();currentSize = fos.getChannel().position();streamsToFlush.add(fos);}padFile(fos);byte[] buf = Util.marshallTxnEntry(hdr, txn);if (buf == null || buf.length == 0) {throw new IOException("Faulty serialization for header " +"and txn");}Checksum crc = makeChecksumAlgorithm();crc.update(buf, 0, buf.length);oa.writeLong(crc.getValue(), "txnEntryCRC");Util.writeTxnBytes(oa, buf);return true;從對事務日志的底底層代碼分析中可以看出,在 datadir 配置參數路徑下存放著 ZooKeeper 服務器所有的事務日志,所有事務日志的命名方法都是“log.+ 該條事務會話的 zxid”。
數據快照
一個快照可以看作是當前系統或軟件服務運行狀態和數據的副本。在 ZooKeeper 中,數據快照的作用是將內存數據結構存儲到本地磁盤中。
因此,從設計的角度說,數據快照與內存數據的邏輯結構一樣,都使用 DataTree 結構。在 ZooKeeper 服務運行的過程中,數據快照每間隔一段時間,就會把 ZooKeeper 內存中的數據存儲到磁盤中,快照文件是間隔一段時間后對內存數據的備份。
因此,與內存數據相比,快照文件的數據具有滯后性。而與上面介紹的事務日志文件一樣,在創建數據快照文件時,也是使用 zxid 作為文件名稱。
在代碼層面,ZooKeeper 通過 FileTxnSnapLog 類來實現數據快照的相關功能。如下圖所示,在FileTxnSnapLog 類的內部,最核心的方法是 save 方法,在 save 方法的內部,首先會創建數據快照文件,之后調用 FileSnap 類對內存數據進行序列化,并寫入到快照文件中。
public void save(DataTree dataTree,ConcurrentHashMap<Long, Integer> sessionsWithTimeouts,boolean syncSnap)throws IOException {long lastZxid = dataTree.lastProcessedZxid;File snapshotFile = new File(snapDir, Util.makeSnapshotName(lastZxid));LOG.info("Snapshotting: 0x{} to {}", Long.toHexString(lastZxid),snapshotFile);snapLog.serialize(dataTree, sessionsWithTimeouts, snapshotFile, syncSnap);}小結
我們知道在 ZooKeeper 服務的運行過程中,會涉及內存數據、事務日志、數據快照這三種數據文件。從存儲位置上來說,事務日志和數據快照一樣,都存儲在本地磁盤上;而從業務角度來講,內存數據就是我們創建數據節點、添加監控等請求時直接操作的數據。事務日志數據主要用于記錄本地事務性會話操作,用于 ZooKeeper 集群服務器之間的數據同步。事務快照則是將內存數據持久化到本地磁盤。
要注意的一點是,數據快照是每間隔一段時間才把內存數據存儲到本地磁盤,因此數據并不會一直與內存數據保持一致。在單臺 ZooKeeper 服務器運行過程中因為異常而關閉時,可能會出現數據丟失等情況。
總結
以上是生活随笔為你收集整理的Apache ZooKeeper - ZK的数据和文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Apache ZooKeeper - 集
- 下一篇: Apache ZooKeeper -从初