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