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的處理方式是一樣的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章