前言:
自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) {
// 方法处理就是:清理这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 retainedTxnLogs = new HashSet();
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 files = new ArrayList();
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进行比较是合适的)。