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. 發現與總結
- 雖然類名是ContainerManager看似他只會清除容器節點,其實清理的節點包含容器節點和臨時節點兩種
- 清理兩種節點爲什麼只是發起了deleteContainer請求,因爲服務端對於deleteContainer和delete的處理方式是一樣的