zookeeper源碼解析-日誌快照清理過程

zk版本:3.5.6

1.引入

在前面介紹單機啓動zk服務時,我們提到過啓動時會創建DatadirCleanupManager對象,用於清理多餘的日誌快照數據,現在我們來看一下它是如何實現的。

2.清理數據

QuorumPeerMain.java
--------------------------

 DatadirCleanupManager purgeMgr = new DatadirCleanupManager(config
                .getDataDir(), config.getDataLogDir(), config
                .getSnapRetainCount(), config.getPurgeInterval());
        // 觸發定時任務,定時清除數據和快照文件
        purgeMgr.start();


觸發清理zk數據的過程:

  1. 根據配置的快照目錄,事務日誌目錄等創建DatadirCleanupManager對象
  2. 啓動定時請求任務

由於創建對象比較簡單,只是簡單的修改對象的屬性。所以我們直接從start方法開始介紹:

DatadirCleanupManager.java
--------------------------

   public void start() {
        // 防止重複執行
        if (PurgeTaskStatus.STARTED == purgeTaskStatus) {
            return;
        }
        if (purgeInterval <= 0) {
            LOG.info("Purge task is not scheduled.");
            return;
        }

        timer = new Timer("PurgeTask", true);
        // 定時任務
        TimerTask task = new PurgeTask(dataLogDir, snapDir, snapRetainCount);
        timer.scheduleAtFixedRate(task, 0, TimeUnit.HOURS.toMillis(purgeInterval));

        // 標識清理服務啓動成功
        purgeTaskStatus = PurgeTaskStatus.STARTED;
    }

清理事務日誌和快照流程:

  1. 標識服務已經成功,防止服務被啓動多次
  2. 創建一個定時任務PurgeTask,這就是清理數據的核心邏輯,線程會執行PurgeTask.run方法。該方法的簡明時序圖如下:
    PurgeTask清理數據時序圖
DatadirCleanupManager.java
----------------------------


    static class PurgeTask extends TimerTask {
        private File logsDir; // 事務日誌目錄
        private File snapsDir; // 快照目錄
        private int snapRetainCount; // 保持快照個數
        .....
    
    // 執行清理工作
    public void run() {
        PurgeTxnLog.purge(logsDir, snapsDir, snapRetainCount);
    }
    
PurgeTxnLog.java
-------------------

  public static void purge(File dataDir, File snapDir, int num) throws IOException {
        // 硬編碼控制快照數大於3
        if (num < 3) {
            throw new IllegalArgumentException(COUNT_ERR_MSG);
        }
        // 校驗並創建數據文件(事務)對象和快照文件對象
        FileTxnSnapLog txnLog = new FileTxnSnapLog(dataDir, snapDir);
        // 發現最近的快照文件
        List<File> snaps = txnLog.findNRecentSnapshots(num);
        int numSnaps = snaps.size();
        if (numSnaps > 0) {
            //  snaps.get(numSnaps - 1)最後一個可以保存的快照
            // 清除老的快照
            purgeOlderSnapshots(txnLog, snaps.get(numSnaps - 1));
        }
    }
    

在清理代碼中主要需要注意的方法是:findNRecentSnapshots和purgeOlderSnapshots。

  • findNRecentSnapshots方法:按照zxid降序排序快照文件,並添加最近的幾個快照文件到集合中。
FileSnap.java
----------------


   // 最近的快照文件
    public List<File> findNRecentSnapshots(int n) throws IOException {
        // 快照文件按照zxid降序
        List<File> files = Util.sortDataDir(snapDir.listFiles(), SNAPSHOT_FILE_PREFIX, false);
        int count = 0;
        List<File> list = new ArrayList<File>();
        for (File f: files) {
            if (count == n)
                break;
            // 如果是快照文件
            if (Util.getZxidFromName(f.getName(), SNAPSHOT_FILE_PREFIX) != -1) {
                count++;
                list.add(f);
            }
        }
        return list;
    }
    
  • purgeOlderSnapshots方法介紹:
  1. 查找最後一個應該保留的zxid
  2. 根據zxid找到需要清除的快照文件列表和事務日誌文件列表
  3. 清除文件列表
PurgeTxnLog.java
-------------------

     /**
     * 清除當前快照對象snapShot對應的zxid之前的數據和快照文件
     * @param snapShot 最後一個要保留的快照文件對象
     */
 static void purgeOlderSnapshots(FileTxnSnapLog txnLog, File snapShot) {
        // 最後要保留的快照zxid
        final long leastZxidToBeRetain = Util.getZxidFromName(
                snapShot.getName(), PREFIX_SNAPSHOT);


        final Set<File> retainedTxnLogs = new HashSet<File>();
        // 添加不用保留的數據文件
        retainedTxnLogs.addAll(Arrays.asList(txnLog.getSnapshotLogs(leastZxidToBeRetain)));
        class MyFileFilter implements FileFilter{
            private final String prefix;
            MyFileFilter(String prefix){
                this.prefix=prefix;
            }
            public boolean accept(File f){
                if(!f.getName().startsWith(prefix + "."))
                    return false;
                if (retainedTxnLogs.contains(f)) {
                    // 如果保存表示需要過濾
                    return false;
                }
                long fZxid = Util.getZxidFromName(f.getName(), prefix);
                if (fZxid >= leastZxidToBeRetain) {
                    return false;
                }
                return true;
            }
        }
        // 列舉需要刪除的數據文件列表
        File[] logs = txnLog.getDataDir().listFiles(new MyFileFilter(PREFIX_LOG));
        List<File> files = new ArrayList<>();
        if (logs != null) {
            files.addAll(Arrays.asList(logs));
        }

        // 列舉需要刪除的快照文件列表
        File[] snapshots = txnLog.getSnapDir().listFiles(new MyFileFilter(PREFIX_SNAPSHOT));
        if (snapshots != null) {
            files.addAll(Arrays.asList(snapshots));
        }

        for(File f: files)
        {
			......
            if(!f.delete()){
                System.err.println("Failed to remove "+f.getPath());
            }
        }

    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章