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方法。该方法的简明时序图如下:

清除zookeeper docker日志 zookeeper清理_java

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());
            }
        }

    }