ZooKeeper系列之(十四):Znode數據結構

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方法來處理;而讀請求則分散在各個不一樣的方法中被調用。事件

讀請求處理方法主要包括如下幾種:

  1. getData:返回指定節點名稱的關聯數據,返回類型爲byte[]。該方法可指定Watcher參數,當節點關聯數據變動時觸發Watcher。
  2. getACL:返回指定節點名稱的ACL
  3. getChildren:返回指定節點名稱的子節點,返回類型List<String>。該方法可指定Watcher參數,當節點的子節點列表變動時觸發Watcher。
  4. statNode:返回指定節點名稱的狀態,返回類型Stat。該方法可指定Watcher參數,當節點的狀態發生變化時觸發Watcher。

  序列化(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,實現全量數據在集羣間的快速同步。

  1. deserialize:從序列化文件中恢復數據庫和session
  2. serialize:將數據庫和session序列化保存到文件

 

四、基本操做

寫請求的具體實現統一在processTxn中被調用,主要則包括如下幾種寫請求:

  1. createNode:建立節點,該方法會觸發該節點的父節點的getChildren方法註冊的Watcher。
  2. deleteNode:刪除節點,該方法會觸發兩種Watcher,一是該節點的父節點的getChildren方法註冊的Watcher;二是該節點自身的getData方法註冊的Watcher。
  3. setData:設置節點關聯數據,該方法會觸發註冊在該節點上數據變動類型的Watcher
  4. setACL: 設置節點的ACL,該方法不會觸發Watcher。

下面舉幾個實際的例子來分析它的代碼邏輯。

createNode

新建路徑,參數是路徑名稱、關聯數據和可選的Watcher,建立一個DataNode,設置DataNode的stat和data[],將建立成功的DataNode添加到nodes中,key設置爲建立節點的名稱。而且將建立的DataNode添加到父節點的children中。

最後觸發已註冊到NodeCreated事件的Watcher。

getData

獲取節點關聯數據,服務端示例代碼:

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)接口會被調用。

statNode

獲取節點狀態。主要代碼以下:

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被調用。

getChildren

獲取節點的子節點。主要代碼以下:

DataNode n = nodes.get(path); 
n.copyStat(stat); 
List<String> children=new ArrayList<String>(n.getChildren()); 
return children;

 

setData

更新節點關聯數據,返回更新後的該節點狀態。

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方法。

deleteNode

刪除節點,輸入參數:

一、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。

相關文章
相關標籤/搜索