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数据的过程:
- 根据配置的快照目录,事务日志目录等创建DatadirCleanupManager对象
- 启动定时请求任务
由于创建对象比较简单,只是简单的修改对象的属性。所以我们直接从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;
}
清理事务日志和快照流程:
- 标识服务已经成功,防止服务被启动多次
- 创建一个定时任务PurgeTask,这就是清理数据的核心逻辑,线程会执行
PurgeTask.run
方法。该方法的简明时序图如下:
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方法介绍:
- 查找最后一个应该保留的zxid
- 根据zxid找到需要清除的快照文件列表和事务日志文件列表
- 清除文件列表
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());
}
}
}