ZooKeeper中數據平時都在內存中經過一個叫ZkDataBase的類來管理維護的,同時ZooKeeper提供了Snapshot(快照)的方式能夠將ZkDataBase持久化到磁盤,防止數據丟失。node
ZkDataBase經過DataTree保存整個樹形數據結構。一個DataTree的每一個節點是DataNode來表示。數據庫
以上就是ZooKeeper中的znode的數據結構了。數組
ZkDataBase讀數據和寫數據的實現邏輯都是經過DataTree實現的,主要包括getNode、getData、getACL、getChildren等操做接口。session
一、DataTree數據結構
Zookeeper的業務數據都經過名爲DataTree的樹形數據結構來維護,DataTree的葉子節點爲DataNode。spa
DataTree經過一個名爲nodes的HashMap維護整個樹的數據,nodes屬性定義以下:code
private final ConcurrentHashMap<String, DataNode> nodes;對象
這個HashMap的key是節點路徑名稱,value是DataNode對象,每一個DataNode表示了該節點路徑的數據和狀態。接口
Zookeeper客戶端的讀請求和寫請求達到DataTree時稍微有所區別,寫請求又稱寫事務,統一經過processTxn方法來處理;而讀請求則分散在各個不一樣的方法中被調用。事件
讀請求處理方法主要包括如下幾種:
序列化(serialize)
序列化,將DataTree保存到磁盤。循環將數據節點序列化到磁盤,用到serializeNode方法。該方法是deserialize方法的逆過程,具體代碼可直接參考源碼。
反序列化(deserialize)
反序列化,從磁盤導入全部節點數據,保存到nodes中。
主要代碼摘取以下:
nodes.clear(); String path = ia.readString("path"); while (!"/".equals(path)) { DataNode node = new DataNode(); ia.readRecord(node, "node") nodes.put(path, node); int lastSlash = path.lastIndexOf('/'); if (lastSlash == -1) { root = node; } else { String parentPath = path.substring(0, lastSlash); DataNode parent = nodes.get(parentPath); parent.addChild(path.substring(lastSlash + 1)); long eowner = node.stat.getEphemeralOwner(); } path = ia.readString("path"); } nodes.put("/", root);
序列化和反序列化是磁盤文件相關操做,想要進一步瞭解細節能夠看源碼。
二、DataNode
DataNode是數據節點的定義,包含了數據節點的數據、訪問控制、狀態、子節點列表等屬性,定義以下:
public class DataNode implements Record { byte data[]; Long acl; public StatPersisted stat; private Set<String> children = null; }
三、SnapShot
SnapShot提供了ZkDataBase的持久化存儲,它主要實現了序列化和反序列化兩個接口。經過這個接口能夠在集羣間快速傳遞SnapShot,實現全量數據在集羣間的快速同步。
四、基本操做
寫請求的具體實現統一在processTxn中被調用,主要則包括如下幾種寫請求:
下面舉幾個實際的例子來分析它的代碼邏輯。
新建路徑,參數是路徑名稱、關聯數據和可選的Watcher,建立一個DataNode,設置DataNode的stat和data[],將建立成功的DataNode添加到nodes中,key設置爲建立節點的名稱。而且將建立的DataNode添加到父節點的children中。
最後觸發已註冊到NodeCreated事件的Watcher。
獲取節點關聯數據,服務端示例代碼:
public byte[] getData(String path, Stat stat, Watcher watcher) { DataNode n = nodes.get(path); synchronized (n) { n.copyStat(stat); if (watcher != null) { dataWatches.addWatch(path, watcher); } return n.data; } }
獲取給定的path節點關聯的數據,該方法提供一個Watcher參數,當該參數不爲空時,DataTree會記錄該path關聯的Watcher,而且在該path數據發生變化時觸發該Watcher,一樣客戶端的Watcher監控事件此時將會被觸發,該Watcher定義的process(WatchedEvent event)接口會被調用。
獲取節點狀態。主要代碼以下:
Stat stat = new Stat(); DataNode n = nodes.get(path); synchronized (n) { n.copyStat(stat); return stat; }
從nodes數組中獲取path關聯的DataNode屬性,若是有Watcher則將warcher添加到dataWatches數組中,下次該DataNode變化時會觸發dataWatches中的watcher被調用。
獲取節點的子節點。主要代碼以下:
DataNode n = nodes.get(path); n.copyStat(stat); List<String> children=new ArrayList<String>(n.getChildren()); return children;
更新節點關聯數據,返回更新後的該節點狀態。
Stat s = new Stat(); DataNode n = nodes.get(path); byte lastdata[] = null; synchronized (n) { lastdata = n.data; n.data = data; n.stat.setMtime(time); n.stat.setMzxid(zxid); n.stat.setVersion(version); n.copyStat(s); } dataWatches.triggerWatch(path, EventType.NodeDataChanged); return s;
從新設置DataNode的data[]內容,觸發該DataNode上已經註冊的觀察者,觸發這些觀察者的process方法。
刪除節點,輸入參數:
一、String path:被刪除節點的名稱。
二、long zxid:當前zxid。
關鍵代碼:
nodes.remove(path); DataNode parent = nodes.get(parentName); synchronized (parent) { parent.removeChild(childName); parent.stat.setPzxid(zxid); }
刪除指定路徑的節點,同時修改該節點的父節點的子節點列表,完成以上操做後再觸發監聽在該節點上的Watcher事件,注意這裏被觸發的Watcher事件包括兩種,一種是該節點自身的Watcher,一種是父節點的getChildren監聽的Watcher。