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。