zk源码阅读8:数据模型之限额: Quotas以及PathTrie和StatsTrack

前言

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
官网
配额讲解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容