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。

相关文章
相关标签/搜索