zookeeper源码解析(十二)

本文深入探讨Zookeeper中的DataTree数据结构,详细解析了如cachedApproximateDataSize、isSpecialPath、copyStatPersisted、copyStat、updateCountBytes、createNode和deleteNode等关键函数的实现和作用,帮助理解Zookeeper内部的数据管理和操作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2021SC@SDUSC

DataTree函数方法

cachedApproximateDataSize:

    //返回所有DataNode的路径和数据所占的空间大小
    public long cachedApproximateDataSize() {
        return nodeDataSize.get();
    }

isSpecialPath: 

    //判断是否是特殊路径(/,/zookeeper,/zookeeper/config,/zookeeper/quota)
    boolean isSpecialPath(String path) {
        return rootZookeeper.equals(path)
               || procZookeeper.equals(path)
               || quotaZookeeper.equals(path)
               || configZookeeper.equals(path);
    }

copyStatPersisted,copyStat: 

    //一个静态的工具方法,使得to存储的实例变量与from存储的实例变量完全一致
    //即实现StatPersisted类型的拷贝
    public static void copyStatPersisted(StatPersisted from, StatPersisted to) {
        to.setAversion(from.getAversion());
        to.setCtime(from.getCtime());
        to.setCversion(from.getCversion());
        to.setCzxid(from.getCzxid());
        to.setMtime(from.getMtime());
        to.setMzxid(from.getMzxid());
        to.setPzxid(from.getPzxid());
        to.setVersion(from.getVersion());
        to.setEphemeralOwner(from.getEphemeralOwner());
    }

    

    //同上,是一个静态的工具方法,使得to存储的实例变量与from存储的实例变量完全一致
    //即实现Stat类型的拷贝
    public static void copyStat(Stat from, Stat to) {
        to.setAversion(from.getAversion());
        to.setCtime(from.getCtime());
        to.setCversion(from.getCversion());
        to.setCzxid(from.getCzxid());
        to.setMtime(from.getMtime());
        to.setMzxid(from.getMzxid());
        to.setPzxid(from.getPzxid());
        to.setVersion(from.getVersion());
        to.setEphemeralOwner(from.getEphemeralOwner());
        to.setDataLength(from.getDataLength());
        to.setNumChildren(from.getNumChildren());
    }

updateCountBytes: 

    public void updateCountBytes(String lastPrefix, long bytesDiff, int countDiff) {
        //获取对应的Stat节点路径,通过路径由哈希表获取节点对象
        String statNode = Quotas.statPath(lastPrefix);
        DataNode node = nodes.get(statNode);

        StatsTrack updatedStat = null;
        if (node == null) {
            //Stat节点不存在,报错
            LOG.error("Missing count node for stat {}", statNode);
            return;
        }
        synchronized (node) {
            updatedStat = new StatsTrack(new String(node.data));
            updatedStat.setCount(updatedStat.getCount() + countDiff);
            updatedStat.setBytes(updatedStat.getBytes() + bytesDiff);
            node.data = updatedStat.toString().getBytes();
        }
        //获取对应的limit节点路径,通过路径由哈希表获取节点对象
        String quotaNode = Quotas.quotaPath(lastPrefix);
        node = nodes.get(quotaNode);
        StatsTrack thisStats = null;
        if (node == null) {
            //limit节点不存在,报错
            LOG.error("Missing count node for quota {}", quotaNode);
            return;
        }
        synchronized (node) {
            thisStats = new StatsTrack(new String(node.data));
        }
        if (thisStats.getCount() > -1 && (thisStats.getCount() < updatedStat.getCount())) {
            LOG.warn(
                "Quota exceeded: {} count={} limit={}",
                lastPrefix,
                updatedStat.getCount(),
                thisStats.getCount());
        }
        if (thisStats.getBytes() > -1 && (thisStats.getBytes() < updatedStat.getBytes())) {
            LOG.warn(
                "Quota exceeded: {} bytes={} limit={}",
                lastPrefix,
                updatedStat.getBytes(),
                thisStats.getBytes());
        }
    }

createNode: 

    //给DataTree创建新节点,调用重载函数,outputStat参数为null
    public void createNode(final String path, byte[] data, List<ACL> acl, long ephemeralOwner, int parentCVersion, long zxid, long time) throws NoNodeException, NodeExistsException {
        createNode(path, data, acl, ephemeralOwner, parentCVersion, zxid, time, null);
    }


    //创建节点,参数依次为
    //path:节点路径;data:节点存储的数据;acl:节点的acl值;
    //ephemeralOwner:节点所属的会话id,-1表示不是临时节点;zxid:事务id;
    //time:创建时间;outputStat:存储Stat输出结果
    public void createNode(final String path, byte[] data, List<ACL> acl, long ephemeralOwner, int parentCVersion, long zxid, long time, Stat outputStat) throws KeeperException.NoNodeException, KeeperException.NodeExistsException {
        //通过获取最后一个斜杆的下标,将path分割为parentName和childName
        int lastSlash = path.lastIndexOf('/');
        String parentName = path.substring(0, lastSlash);
        String childName = path.substring(lastSlash + 1);

        //用事务id,创建时间,所属会话id三个参数创建StatPersisted对象
        StatPersisted stat = createStat(zxid, time, ephemeralOwner);
        //获取我们要创建的节点的父节点对象
        DataNode parent = nodes.get(parentName);
        //如果没有父节点,抛出NoNodeException
        if (parent == null) {
            throw new KeeperException.NoNodeException();
        }
        synchronized (parent) {
            //首先将ACL添加到ACL缓存中,以避免在模糊快照同步过程中ACL不被创建竞争条件。
            // 这是最简单的修复方法,如果模糊快照的ACL映射中已经计入了ACL引用计数,则可能会再次添加ACL引用计数,deleteNode txn也可能发生这种情况,但至少不会导致ACL不存在问题。
            // 稍后我们可以在从磁盘加载快照时从 ACL 映射中审计和删除所有未引用的 ACL
            Long longval = aclCache.convertAcls(acl);

            Set<String> children = parent.getChildren();
            if (children.contains(childName)) {
                throw new KeeperException.NodeExistsException();
            }

            nodes.preChange(parentName, parent);
            if (parentCVersion == -1) {
                parentCVersion = parent.stat.getCversion();
                parentCVersion++;
            }
            //有可能我们会在模糊范围内重放创建然后删除的节点的txns,并且它不存在于快照中,
            // 因此重放创建可能会恢复cversion和pzxid,需要检查并且只有在它出现时才更新更大。
            if (parentCVersion > parent.stat.getCversion()) {
                parent.stat.setCversion(parentCVersion);
                parent.stat.setPzxid(zxid);
            }
            DataNode child = new DataNode(data, longval, stat);
            parent.addChild(childName);
            nodes.postChange(parentName, parent);
            nodeDataSize.addAndGet(getNodeSize(path, child.data));
            nodes.put(path, child);
            EphemeralType ephemeralType = EphemeralType.get(ephemeralOwner);
            if (ephemeralType == EphemeralType.CONTAINER) {
                containers.add(path);
            } else if (ephemeralType == EphemeralType.TTL) {
                ttls.add(path);
            } else if (ephemeralOwner != 0) {
                HashSet<String> list = ephemerals.get(ephemeralOwner);
                if (list == null) {
                    list = new HashSet<String>();
                    ephemerals.put(ephemeralOwner, list);
                }
                synchronized (list) {
                    list.add(path);
                }
            }
            if (outputStat != null) {
                child.copyStat(outputStat);
            }
        }
        //检查它是不是zookeeper节点的一个子节点
        if (parentName.startsWith(quotaZookeeper)) {
            //检查它是不是limit节点
            if (Quotas.limitNode.equals(childName)) {
                //这是limit节点
                //获取parentName中‘/zookeeper/quota’后面的部分,加入字典树(我们前文说过zookeeper用字典树记录配额节点)
                pTrie.addPath(parentName.substring(quotaZookeeper.length()));
            }
            if (Quotas.statNode.equals(childName)) {
                updateQuotaForPath(parentName.substring(quotaZookeeper.length()));
            }
        }
        //检查是否更新节点配额
        String lastPrefix = getMaxPrefixWithQuota(path);
        long bytes = data == null ? 0 : data.length;
        if (lastPrefix != null) {
            updateCountBytes(lastPrefix, bytes, 1);
        }
        updateWriteStat(path, bytes);
        dataWatches.triggerWatch(path, Event.EventType.NodeCreated);
        childWatches.triggerWatch(parentName.equals("") ? "/" : parentName, Event.EventType.NodeChildrenChanged);
    }

deleteNode: 

    public void deleteNode(String path, long zxid) throws KeeperException.NoNodeException {
        int lastSlash = path.lastIndexOf('/');
        String parentName = path.substring(0, lastSlash);
        String childName = path.substring(lastSlash + 1);

        //在拍摄模糊快照期间,孩子可能已经被删除,但我们仍然需要在这里更新 pzxid,然后再抛出异常,因为没有这样的孩子
        DataNode parent = nodes.get(parentName);
        if (parent == null) {
            throw new KeeperException.NoNodeException();
        }
        synchronized (parent) {
            nodes.preChange(parentName, parent);
            parent.removeChild(childName);
            //仅当 zxid 大于当前 pzxid 时才更新 pzxid,否则我们可能会覆盖一些由 create Txn 设置的更高的 pzxid,这可能导致 cversion 和 pzxid 不一致
            if (zxid > parent.stat.getPzxid()) {
                parent.stat.setPzxid(zxid);
            }
            nodes.postChange(parentName, parent);
        }

        DataNode node = nodes.get(path);
        if (node == null) {
            throw new KeeperException.NoNodeException();
        }
        nodes.remove(path);
        synchronized (node) {
            aclCache.removeUsage(node.acl);
            nodeDataSize.addAndGet(-getNodeSize(path, node.data));
        }

        //同步容器和 ttls 修改
        synchronized (parent) {
            long eowner = node.stat.getEphemeralOwner();
            EphemeralType ephemeralType = EphemeralType.get(eowner);
            if (ephemeralType == EphemeralType.CONTAINER) {
                containers.remove(path);
            } else if (ephemeralType == EphemeralType.TTL) {
                ttls.remove(path);
            } else if (eowner != 0) {
                Set<String> nodes = ephemerals.get(eowner);
                if (nodes != null) {
                    synchronized (nodes) {
                        nodes.remove(path);
                    }
                }
            }
        }

        if (parentName.startsWith(procZookeeper) && Quotas.limitNode.equals(childName)) {
            //删除字典树中的该节点
            //更新这棵字典树
            pTrie.deletePath(parentName.substring(quotaZookeeper.length()));
        }

        //检查是否更新该节点的配额
        String lastPrefix = getMaxPrefixWithQuota(path);
        if (lastPrefix != null) {
            int bytes = 0;
            synchronized (node) {
                bytes = (node.data == null ? 0 : -(node.data.length));
            }
            updateCountBytes(lastPrefix, bytes, -1);
        }

        updateWriteStat(path, 0L);

        if (LOG.isTraceEnabled()) {
            ZooTrace.logTraceMessage(
                LOG,
                ZooTrace.EVENT_DELIVERY_TRACE_MASK,
                "dataWatches.triggerWatch " + path);
            ZooTrace.logTraceMessage(
                LOG,
                ZooTrace.EVENT_DELIVERY_TRACE_MASK,
                "childWatches.triggerWatch " + parentName);
        }

        WatcherOrBitSet processed = dataWatches.triggerWatch(path, EventType.NodeDeleted);
        childWatches.triggerWatch(path, EventType.NodeDeleted, processed);
        childWatches.triggerWatch("".equals(parentName) ? "/" : parentName, EventType.NodeChildrenChanged);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值