zookeeper源码解析-节点清理

zk版本:3.5.6

1. 引入

在3.5.6版本中,有两种节点需要清理:临时节点(会话结束,会被清除)和容器节点(如果没有子节点,会被清理)。

2. 节点清理

清理节点的操作是通过ContainerManager类完成,在单机启动的博客中其他可以看到它的身影。

ZooKeeperServerMain.java
------------------------
            public void runFromConfig(ServerConfig config)
            throws IOException, AdminServerException {
     
            // 定时清除容器节点
            containerManager = new ContainerManager(zkServer.getZKDatabase(), zkServer.firstProcessor,
                    Integer.getInteger("znode.container.checkIntervalMs", (int) TimeUnit.MINUTES.toMillis(1)),
                    Integer.getInteger("znode.container.maxPerMinute", 10000)
            );
             containerManager.start();

containerManager.start就是清理节点的入口:

ContainerManager.java
---------------------

    public void start() {
        if (task.get() == null) {
            // 检查并清除无用的节点task
            TimerTask timerTask = new TimerTask() {
                @Override
                public void run() {
                    try {

                        checkContainers();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        LOG.info("interrupted");
                        cancel();
                    } catch ( Throwable e ) {
                        LOG.error("Error checking containers", e);
                    }
                }
            };
            // 定时执行
            if (task.compareAndSet(null, timerTask)) {
                timer.scheduleAtFixedRate(timerTask, checkIntervalMs,
                        checkIntervalMs);
            }
        }
    }

和日志快照清理差不多,这个也是创建一个定时器,然后根据配置定时执行清理操作,主要逻辑在checkContainers完成。

ContainerManager.java
---------------------

    public void checkContainers()
            throws InterruptedException {
        long minIntervalMs = getMinIntervalMs();
        for (String containerPath : getCandidates()) {
            long startMs = Time.currentElapsedTime();

            ByteBuffer path = ByteBuffer.wrap(containerPath.getBytes());
            // 删除节点请求(由于删除普通节点和容器节点的操作是一样的,这里统一使用)
            Request request = new Request(null, 0, 0,
                    ZooDefs.OpCode.deleteContainer, path, null);
            try {
                        containerPath);
                // 处理删除节点请求
                requestProcessor.processRequest(request);
            } catch (Exception e) {
                LOG.error("Could not delete container: {}",
                        containerPath, e);
            }

            // 等待处理一下个路径
            long elapsedMs = Time.currentElapsedTime() - startMs;
            long waitMs = minIntervalMs - elapsedMs;
            if (waitMs > 0) {
                Thread.sleep(waitMs);
            }
        }
    }

主要步骤是:

(1) 找到需要清理的节点

(2) 发起deleteContainer请求,清理节点

(3) 通过通过间隔时间控制执行的周期

我们现在来分析一下是如何找到需要清理的节点。

ContainerManager.java
---------------------
  protected Collection<String> getCandidates() {
        Set<String> candidates = new HashSet<String>();
        // 容器节点中没有子节点
        for (String containerPath : zkDb.getDataTree().getContainers()) {
            DataNode node = zkDb.getDataTree().getNode(containerPath);
            /*
                cversion>0 保证已经操作过子节点
             */
            if ((node != null) && (node.stat.getCversion() > 0) &&
                    (node.getChildren().size() == 0)) {
                candidates.add(containerPath);
            }
        }
        // 过期节点
        for (String ttlPath : zkDb.getDataTree().getTtls()) {
            DataNode node = zkDb.getDataTree().getNode(ttlPath);
            if (node != null) {
                Set<String> children = node.getChildren();
                if ((children == null) || (children.size() == 0)) {

                    if ( EphemeralType.get(node.stat.getEphemeralOwner()) == EphemeralType.TTL ) {
                        long elapsed = getElapsed(node);
                        long ttl = EphemeralType.TTL.getValue(node.stat.getEphemeralOwner());
                        if ((ttl != 0) && (getElapsed(node) > ttl)) {
                            candidates.add(ttlPath);
                        }
                    }
                }
            }
        }
        return candidates;
    }

通过上面的代码可知,查找的节点类型有:

(1) 容器节点(要求节点的cversion>0,因为cversion表示对此znode的子节点进行的更改次数,即表示拥有过子节点)

(2) 临时节点

3. 触发节点清理的位置

单机启动时会触发节点清理,集群启动方式也会触发清理,它在哪里呢?其实就是在LeaderZooKeeperServer类中:

LeaderZooKeeperServer.java
--------------------------

    private synchronized void setupContainerManager() {
        containerManager = new ContainerManager(getZKDatabase(), prepRequestProcessor,
                Integer.getInteger("znode.container.checkIntervalMs", (int) TimeUnit.MINUTES.toMillis(1)),
                Integer.getInteger("znode.container.maxPerMinute", 10000)
                );
    }

即在leader启动时会触发节点清理,但是在其他角色中没有这个操作的,这是因为节点清理最终是发送deleteContainer请求实现的,只要发送一次就行,集群中的所有zookeeper节点都会执行。

3. 发现与总结

  1. 虽然类名是ContainerManager看似他只会清除容器节点,其实清理的节点包含容器节点和临时节点两种
  2. 清理两种节点为什么只是发起了deleteContainer请求,因为服务端对于deleteContainer和delete的处理方式是一样的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章