Zookeeper源码解析-自动清理事务日志、快照日志功能分析

前言:

自Zookeeper-3.4.0版本开始,就提供了自动清理事务日志和快照日志的功能。

我们可以想一下,如果不清理这些日志会怎样?貌似短期也不会怎样,但是由于这些日志是直接落入当前磁盘的,所以长期以往,磁盘肯定会被占满,导致zookeeper服务无法正常提供。

本文就介绍下这个自动清理日志的功能。

1.配置自动清理

配置的方式很简单,就是在zoo.cfg中添加以下两个配置即可,示例如下:

# 保存3个快照,3个日志文件
autopurge.snapRetainCount=3
# 间隔1个小时执行一次清理
autopurge.purgeInterval=1

autopurge.snapRetainCount指定的是最多保留几个日志文件

autopurge.purgeInterval指定的是多久去清理一次

需要注意的是:这个配置默认是不开启的,所以需要我们手动添加。

2.源码分析清理功能实现

那么这个清理功能时如何实现的呢?我们直接在源码中查找配置出现的地方,最终发现在DatadirCleanupManager中有相关的使用,那么我们就来分析下这个类

2.1 DatadirCleanupManager结构分析

public class DatadirCleanupManager {

    // 任务状态
    public enum PurgeTaskStatus {
        NOT_STARTED, STARTED, COMPLETED;
    }

    // 默认状态
    private PurgeTaskStatus purgeTaskStatus = PurgeTaskStatus.NOT_STARTED;

    // 快照日志路径
    private final String snapDir;

    // 事务日志路径
    private final String dataLogDir;

    // 最多保留的日志文件数
    private final int snapRetainCount;

    // 执行频率
    private final int purgeInterval;

    // 调度器
    private Timer timer;
 
    // 构造器
    public DatadirCleanupManager(String snapDir, String dataLogDir, int snapRetainCount,
            int purgeInterval) {
        this.snapDir = snapDir;
        this.dataLogDir = dataLogDir;
        this.snapRetainCount = snapRetainCount;
        this.purgeInterval = purgeInterval;
        LOG.info("autopurge.snapRetainCount set to " + snapRetainCount);
        LOG.info("autopurge.purgeInterval set to " + purgeInterval);
    }
}

那么这个构造器是在哪里被调用的呢?

源码一通调用,发现入口是:QuorumPeerMain.initializeAndRun()方法,具体如下

public class QuorumPeerMain {
	protected void initializeAndRun(String[] args) throws ConfigException, IOException {
        QuorumPeerConfig config = new QuorumPeerConfig();
        if (args.length == 1) {
            config.parse(args[0]);
        }

        // 在这里开启自动清理任务
        DatadirCleanupManager purgeMgr = new DatadirCleanupManager(config
                .getDataDir(), config.getDataLogDir(), config
                .getSnapRetainCount(), config.getPurgeInterval());
        purgeMgr.start();
        ...
        }
}

2.2 DatadirCleanupManager.start()

public class DatadirCleanupManager {
 
    public void start() {
        // 任务状态及参数校验
        if (PurgeTaskStatus.STARTED == purgeTaskStatus) {
            LOG.warn("Purge task is already running.");
            return;
        }
        if (purgeInterval <= 0) {
            LOG.info("Purge task is not scheduled.");
            return;
        }

        // 创建调度器
        timer = new Timer("PurgeTask", true);
        TimerTask task = new PurgeTask(dataLogDir, snapDir, snapRetainCount);
        // 以purgeInterval执行频率执行PurgeTask
        timer.scheduleAtFixedRate(task, 0, TimeUnit.HOURS.toMillis(purgeInterval));

        purgeTaskStatus = PurgeTaskStatus.STARTED;
    }

}

start()方法主要是启动了一个Timer定时调度器,执行频率为purgeInterval小时,执行任务为PurgeTask

2.3 PurgeTask

static class PurgeTask extends TimerTask {
        private String logsDir;
        private String snapsDir;
        private int snapRetainCount;

        public PurgeTask(String dataDir, String snapDir, int count) {
            logsDir = dataDir;
            snapsDir = snapDir;
            snapRetainCount = count;
        }

        @Override
        public void run() {
            LOG.info("Purge task started.");
            try {
                // 最终交由PurgeTxnLog来执行
                PurgeTxnLog.purge(new File(logsDir), new File(snapsDir), snapRetainCount);
            } catch (Exception e) {
                LOG.error("Error occurred while purging.", e);
            }
            LOG.info("Purge task completed.");
        }
    }

2.4 PurgeTxnLog.purge()

public class PurgeTxnLog {
	public static void purge(File dataDir, File snapDir, int num) throws IOException {
        if (num < 3) {
            throw new IllegalArgumentException(COUNT_ERR_MSG);
        }

        FileTxnSnapLog txnLog = new FileTxnSnapLog(dataDir, snapDir);

        // 获取最新的num个快照文件
        List<File> snaps = txnLog.findNRecentSnapshots(num);
        int numSnaps = snaps.size();
        if (numSnaps > 0) {
            // 方法处理就是:清理这num个文件之外的其他文件
            purgeOlderSnapshots(txnLog, snaps.get(numSnaps - 1));
        }
    }
    
    static void purgeOlderSnapshots(FileTxnSnapLog txnLog, File snapShot) {
        // 最新的zxid(num个文件中最小的那个)
        // 只要比这个zxid小的文件都需要被清理
        final long leastZxidToBeRetain = Util.getZxidFromName(
                snapShot.getName(), PREFIX_SNAPSHOT);
        // retainedTxnLogs中是需要被保留的日志
        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;
            }
        }
        // files中添加的是zxid比leastZxidToBeRetain小的所有事务日志文件,是需要被删除的
        List<File> files = new ArrayList<File>();
        File[] fileArray = txnLog.getDataDir().listFiles(new MyFileFilter(PREFIX_LOG));
        if (fileArray != null) {
            files.addAll(Arrays.asList(fileArray));
        }

        // fileArray中添加的是zxid比leastZxidToBeRetain小的所有快照文件,需要被删除
        fileArray = txnLog.getSnapDir().listFiles(new MyFileFilter(PREFIX_SNAPSHOT));
        if (fileArray != null) {
            files.addAll(Arrays.asList(fileArray));
        }

        // 删除老文件
        for(File f: files)
        {
            final String msg = "Removing file: "+
                DateFormat.getDateTimeInstance().format(f.lastModified())+
                "\t"+f.getPath();
            LOG.info(msg);
            System.out.println(msg);
            if(!f.delete()){
                System.err.println("Failed to remove "+f.getPath());
            }
        }
    }
}

总结:

代码不算复杂,直接保留对应数目的文件后,直接把其他的全部删除。

这个其他的意思是通过zxid进行比较过的,zxid较小的所有文件(事务日志和快照日志的后缀就是zxid,所以这里直接通过zxid进行比较是合适的)。

猜你喜欢

转载自blog.csdn.net/qq_26323323/article/details/120873165
今日推荐