前言
DataTree的涉及到PathTrie以及Quotas,所以本节先讲解这两个类
摘要
本节主要讲解
PathTrie,Quotas,StatsTrack的意义
源码分析
意义
这几个类是配合完成zk中某些znode(叶子或目录)的配额限制工作的
ZooKeeper quota机制支持节点个数(znode)和空间大小(字节数)。
在java api里面没有找到直接处理配额的接口,在配额讲解中有cmd的demo,可以参照看一下
Quotas
主要完成配额目录的定义
限制信息包含对某个路径的要求大小(count或者bytes)
状态信息包含对某个路径的实际大小(count或者bytes)
以及提供path转换到对应的限制path和状态path的方法
PathTrie
字典树完成配额目录的增删查
将拥有对应配额属性的节点设置标记属性property,源码会讲
在zk中的目录结构为/zookeeper/quota/xxx(可以有多级目录)/zookeeper_limits
StatsTrack
就是记录某个节点实际的count和bytes长度信息
在zk中的目录结构为/zookeeper/quota/xxx(可以有多级目录)/statNode
源码
Quotas
这个类主要是定义几个管理配额的目录名称,为DataTree处理配额提供定义
public class Quotas {
/** the zookeeper nodes that acts as the management and status node **/
public static final String procZookeeper = "/zookeeper";
/** the zookeeper quota node that acts as the quota
* management node for zookeeper */
public static final String quotaZookeeper = "/zookeeper/quota";//配额目录
/**
* the limit node that has the limit of
* a subtree
*/
public static final String limitNode = "zookeeper_limits";//node限制的结尾后缀
/**
* the stat node that monitors the limit of
* a subtree.
*/
public static final String statNode = "zookeeper_stats";//node实际状态的结尾后缀
/**
* return the quota path associated with this
* prefix
* @param path the actual path in zookeeper.
* @return the limit quota path
*/
public static String quotaPath(String path) {//zk某个节点path转换成对应的limit path
return quotaZookeeper + path +
"/" + limitNode;
}
/**
* return the stat quota path associated with this
* prefix.
* @param path the actual path in zookeeper
* @return the stat quota path
*/
public static String statPath(String path) {//zk某个节点path转换成对应的stat path
return quotaZookeeper + path + "/" +
statNode;
}
}
这里主要注意
/zookeeper/quota/xxx(可以有多级目录)/zookeeper_limits 就是针对/xxx(可以有多级目录)的限制,是理论上的限制
/zookeeper/quota/xxx(可以有多级目录)/statNode 就是针对/xxx(可以有多级目录)的真实状态记录
PathTrie
网上好多地方说
Zookeeper使用Trie树来实现了hierachal namespace,由PathTrie这个类来完成
这个观点我觉得是错的,因为zk的代码里面,PathTrie只是用来管理配额的,如果一个znode没用到配额,那么它就和PathTrie没有关系
实现简介:
看名字就知道是字典书的实现了,源码也基本是这个思路,不懂可以自己去找字典树相关资料
内部类TrieNode
TrieNode是字典树中每个node的情况,主要记录parent,以及children的set,源码如下
static class TrieNode {
boolean property = false;//属性,看源码表现,就是设置了配额的节点
final HashMap<String, TrieNode> children;//记录子节点相对路径 与 TrieNode的mapping
TrieNode parent = null;
/**
* create a trienode with parent
* as parameter
* @param parent the parent of this trienode
*/
private TrieNode(TrieNode parent) {//构造时,设置parent
children = new HashMap<String, TrieNode>();
this.parent = parent;
}
/**
* get the parent of this node
* @return the parent node
*/
TrieNode getParent() {
return this.parent;
}
/**
* set the parent of this node
* @param parent the parent to set to
*/
void setParent(TrieNode parent) {
this.parent = parent;
}
/**
* a property that is set
* for a node - making it
* special.
*/
void setProperty(boolean prop) {
this.property = prop;
}
/** the property of this
* node
* @return the property for this
* node
*/
boolean getProperty() {
return this.property;
}
/**
* add a child to the existing node
* @param childName the string name of the child
* @param node the node that is the child
*/
/*
* 添加childName的相对路径到map,注意:在调用方设置node的parent
*/
void addChild(String childName, TrieNode node) {
synchronized(children) {
if (children.containsKey(childName)) {
return;
}
children.put(childName, node);
}
}
/**
* delete child from this node
* @param childName the string name of the child to
* be deleted
*/
void deleteChild(String childName) {
synchronized(children) {
if (!children.containsKey(childName)) {
return;
}
TrieNode childNode = children.get(childName);
// this is the only child node.
if (childNode.getChildren().length == 1) { //如果这个儿子只有1个儿子,那么就把这个儿子丢掉
childNode.setParent(null);//被删除的子节点,parent设置为空
children.remove(childName);
}
else {
// their are more child nodes
// so just reset property.
childNode.setProperty(false);//否则这个儿子还有其他儿子,标记它不是没有配额限制,这里有个bug,就是数量为0时,也进入这个逻辑
}
}
}
/**
* return the child of a node mapping
* to the input childname
* @param childName the name of the child
* @return the child of a node
*/
//根据childName从map中取出对应的TrieNode
TrieNode getChild(String childName) {
synchronized(children) {
if (!children.containsKey(childName)) {
return null;
}
else {
return children.get(childName);
}
}
}
/**
* get the list of children of this
* trienode.
* @param node to get its children
* @return the string list of its children
*/
//获取子节点String[]列表
String[] getChildren() {
synchronized(children) {
return children.keySet().toArray(new String[0]);
}
}
/**
* get the string representation
* for this node
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Children of trienode: ");
synchronized(children) {
for (String str: children.keySet()) {
sb.append(" " + str);
}
}
return sb.toString();
}
}
这里注意几个问题即可
1.addChild方法时,注意在调用方设置child的parent为当前节点
2.deleteChild方法似乎有bug(或者说不准确),就是把儿子的儿子个数为1当成一种处理,0或者>=2当成另外一种处理
3.property的意义:设置了配额的节点,该属性为true,否则为false
比如说:设置了/a/b/c这个路径拥有配额,那么字典树中,property是这样的
root
/
a
/
b
/
c(property:true)
看完了字典树节点的结构,再看看字典树的维护
PathTrie维护字典树
成员变量以及构造函数
首先成员变量,即字典树的根,以及构造函数如下,比较好理解
private final TrieNode rootNode ;
/**
* construct a new PathTrie with
* a root node of /
*/
public PathTrie() {
this.rootNode = new TrieNode(null);
}
这里要注意root和Quotas类中定义的"/zookeeper/quota"的关系
字典树结构与zk结构的关系
因为PathTrie字典树在zk中是管理配额的
两者有一个默认的目录的转换,即Quotas类中定义的"/zookeeper/quota"
举例说明如下
zk中的目录 /zookeeper/quota/a/b/c/zookeeper_limits 对应的PathTrie结构为
root
/
a
/
b
/
c
/
zookeeper_limits
PathTrie中的root即相当于zk目录中的/zookeeper/quota,所以在下面的函数中,path是PathTrie中的path,不是zk中的path,即不会以"/zookeeper/quota"开头
字典树其他主要操作如下:
addPath
字典树的增,这里就是把一个path按照/符号分开,加入字典树
/**
* add a path to the path trie
* @param path
*/
public void addPath(String path) {
if (path == null) {
return;
}
String[] pathComponents = path.split("/");//将路径按照"/" split开
TrieNode parent = rootNode;
String part = null;
if (pathComponents.length <= 1) {
throw new IllegalArgumentException("Invalid path " + path);
}
for (int i=1; i<pathComponents.length; i++) {//从1开始因为路径都是"/"开头的,pathComponents[0]会是""
part = pathComponents[i];
if (parent.getChild(part) == null) {
parent.addChild(part, new TrieNode(parent));//一方面parent将这个child记录在map,一方面将这个child node进行初始化以及设置parent
}
parent = parent.getChild(part);//进入到对应的child
}
parent.setProperty(true);//最后这个节点设置配额属性
}
deletePath
字典树的删
/**
* delete a path from the trie
* @param path the path to be deleted
*/
public void deletePath(String path) {
if (path == null) {
return;
}
String[] pathComponents = path.split("/");
TrieNode parent = rootNode;
String part = null;
if (pathComponents.length <= 1) {
throw new IllegalArgumentException("Invalid path " + path);
}
for (int i=1; i<pathComponents.length; i++) {
part = pathComponents[i];
if (parent.getChild(part) == null) {
//the path does not exist
return;
}
parent = parent.getChild(part);
LOG.info("{}",parent);
}
TrieNode realParent = parent.getParent();//得到被删除TrieNode的parent
realParent.deleteChild(part);//将这个node从parent的子列表中删除
}
说明:
deletePath传入的参数,并不会一定精确到叶子节点,也就是可能会到某个目录,再调用realParent.deleteChild进行TrieNode的deleteChild操作
findMaxPrefix
字典树的查
这个函数的作用就是根据path按路径远近找到最近的拥有配额标记的节点的路径
实现方式就是
1.将path按/分开,进行字典树查找,从根开始
2.记住路径中最后一个拥有配额标记的TrieNode
源码如下
/**
* return the largest prefix for the input path.
* @param path the input path
* @return the largest prefix for the input path.
*/
public String findMaxPrefix(String path) {//找到最近的一个拥有配额标记的祖先节点
if (path == null) {
return null;
}
if ("/".equals(path)) {
return path;
}
String[] pathComponents = path.split("/");//按照/分开
TrieNode parent = rootNode;
List<String> components = new ArrayList<String>();
if (pathComponents.length <= 1) {
throw new IllegalArgumentException("Invalid path " + path);
}
int i = 1;//因为路径是/开头
String part = null;
StringBuilder sb = new StringBuilder();
int lastindex = -1;
while((i < pathComponents.length)) {
if (parent.getChild(pathComponents[i]) != null) {
part = pathComponents[i];
parent = parent.getChild(part);//一层层到子节点
components.add(part);
if (parent.getProperty()) {//如果对应的子节点有标记
lastindex = i-1;//更新最后一个有标记的节点(也就是最近的有标记的祖先)
}
}
else {
break;
}
i++;
}
for (int j=0; j< (lastindex+1); j++) {
sb.append("/" + components.get(j));
}
return sb.toString();//返回最近的,有标记的祖先的路径
}
clear
/**
* clear all nodes
*/
public void clear() {
for(String child : rootNode.getChildren()) {
rootNode.deleteChild(child);
}
}
StatsTrack类
就是记录某个节点实际的count和bytes长度信息
在zk中的目录结构为/zookeeper/quota/xxx(可以有多级目录)/statNode
源码太简单了,只要注意有参构造函数和属性就行了,其他get set toString就不列出来了
public class StatsTrack {
private int count;
private long bytes;
private String countStr = "count";
private String byteStr = "bytes";
/**
* the stat string should be of the form count=int,bytes=long
* if stats is called with null the count and bytes are initialized
* to -1.
* @param stats the stat string to be intialized with
*/
public StatsTrack(String stats) {//根据 count=int,bytes=long 的一个String进行解析
if (stats == null) {
stats = "count=-1,bytes=-1";
}
String[] split = stats.split(",");
if (split.length != 2) {
throw new IllegalArgumentException("invalid string " + stats);
}
count = Integer.parseInt(split[0].split("=")[1]);
bytes = Long.parseLong(split[1].split("=")[1]);
}
}
思考
Quotas以及PathTrie和StatsTrack三个类的意义
上面已经讲了
Quotas#limitNode 以及 Quotas#statNode分别是干吗用的
这两个都作为后缀,分别记录理论配额限制,和实际的配额状态记录
/zookeeper/quota/xxx(可以有多级目录)/zookeeper_limits 就是针对/xxx(可以有多级目录)的限制,是理论上的限制
/zookeeper/quota/xxx(可以有多级目录)/statNode 就是针对/xxx(可以有多级目录)的真实状态记录
PathTrie中的path和其在zk中path的关系
上面已经讲过,两者相差一个"/zookeeper/quota"目录
PathTrie.TrieNode#property意义
表示当前字典树节点是否有配额属性
PathTrie#deletePath()函数
参数不一定精确到PathTrie的叶子节点
每个节点在zk中都会有对应的配额节点吗,哪怕是初始化的
不是,要显式设置才有,参照配额讲解
或者类似下面的demo,定义到"/zookeeper/quota"这个目录
String path2 = zk.create("/zookeeper/quota/test15",
"".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
String path3 = zk.create("/zookeeper/quota/test15/zookeeper_limits",
"".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
每个配额节点会有对应的状态节点吗
即创建了配额节点,会自动创建状态节点吗
不会,在DataTree#createNode里面是分开按照后缀处理的,即还是需要显式创建.
refer
http://www.cnblogs.com/sunddenly/articles/4087251.html
官网
配额讲解