有些時(shí)候我們需要在業(yè)務(wù)主流程外做一些不影響主流程的操作,比如發(fā)生通知,開啟一個(gè)異步任務(wù),我們不關(guān)心這些額外操作的執(zhí)行成功與否,但不能影響主流程。一般這種場(chǎng)景下,都會(huì)使用異步線程處理,下面帶著源碼,來看下Logback在進(jìn)行日志歸檔的過程中如何通過異步任務(wù)實(shí)現(xiàn)日志壓縮。希望大家舉一反三,領(lǐng)會(huì)其設(shè)計(jì)精髓,方便以后運(yùn)行在自己的代碼中。
場(chǎng)景描述
Logback在進(jìn)行日志歸檔過程中主要處理一下幾件事:
1.文件名轉(zhuǎn)換,將當(dāng)前活動(dòng)文件名轉(zhuǎn)換成歸檔文件名。執(zhí)行該步驟的條件是<appender>配置了<file> 屬性,當(dāng)前活動(dòng)文件名是<file> 屬性值,歸檔文件名是<fileNamePatternStr>的格式。
2.歸檔文件壓縮,執(zhí)行該步驟的條件是<fileNamePatternStr>屬性值中以.gz/.zip等后綴結(jié)尾。
3.歷史歸檔文件刪除,執(zhí)行該步驟的條件是配置了<maxHistory>。
// ch.qos.logback.core.rolling.TimeBasedRollingPolicy#rollover
//文件歸檔操作
public void rollover() throws RolloverFailure {
//歸檔文件名全路徑,如logs/test.log
String elapsedPeriodsFileName = timeBasedFileNamingAndTriggeringPolicy.getElapsedPeriodsFileName();
//歸檔文件名稱,如test.log
String elapsedPeriodStem = FileFilterUtil.afterLastSlash(elapsedPeriodsFileName);
//判斷是否配置壓縮
if (compressionMode == CompressionMode.NONE) {
//判斷<appender>中是否配置了<file>屬性
if (getParentsRawFileProperty() != null) {
//將當(dāng)前活動(dòng)文件名轉(zhuǎn)換為歸檔文件名
renameUtil.rename(getParentsRawFileProperty(), elapsedPeriodsFileName);
}
} else {
if (getParentsRawFileProperty() == null) {
//直接將歸檔文件壓縮
compressionFuture = compressor.asyncCompress(elapsedPeriodsFileName, elapsedPeriodsFileName, elapsedPeriodStem);
} else {
//先根據(jù)<file>創(chuàng)建一個(gè)臨時(shí)文件和一個(gè)新的<file>文件,再將臨時(shí)文件名轉(zhuǎn)換為歸檔文件名,然后將歸檔文件壓縮
compressionFuture = renameRawAndAsyncCompress(elapsedPeriodsFileName, elapsedPeriodStem);
}
}
//判斷是否配置<maxHistory>
if (archiveRemover != null) {
Date now = new Date(timeBasedFileNamingAndTriggeringPolicy.getCurrentTime());
this.cleanUpFuture = archiveRemover.cleanAsynchronously(now);
}
}
這里面歸檔文件的壓縮和歷史文件的刪除都使用的是異步任務(wù)。壓縮操作使用Compressor類處理,刪除操作使用ArchiveRemover類處理
//ch.qos.logback.core.rolling.helper.Compressor#asyncCompress
public Future<?> asyncCompress(String nameOfFile2Compress, String nameOfCompressedFile, String innerEntryName) throws RolloverFailure {
//創(chuàng)建異步任務(wù)
CompressionRunnable runnable = new CompressionRunnable(nameOfFile2Compress, nameOfCompressedFile, innerEntryName);
//獲取任務(wù)線程池
ExecutorService executorService = context.getScheduledExecutorService();
//執(zhí)行異步任務(wù)
Future<?> future = executorService.submit(runnable);
return future;
}
//ch.qos.logback.core.rolling.helper.Compressor.CompressionRunnable
class CompressionRunnable implements Runnable {
final String nameOfFile2Compress;
final String nameOfCompressedFile;
final String innerEntryName;
public CompressionRunnable(String nameOfFile2Compress, String nameOfCompressedFile, String innerEntryName) {
this.nameOfFile2Compress = nameOfFile2Compress;
this.nameOfCompressedFile = nameOfCompressedFile;
this.innerEntryName = innerEntryName;
}
public void run() {
//調(diào)用Compressor實(shí)例中的compress方法壓縮文件
Compressor.this.compress(nameOfFile2Compress, nameOfCompressedFile, innerEntryName);
}
}
CompressionRunnable 實(shí)際上是沒有返回值的,但是asyncCompress方法還是返回了任務(wù)執(zhí)行結(jié)果,那這個(gè)空的結(jié)果有什么用呢?
//ch.qos.logback.core.rolling.TimeBasedRollingPolicy#stop
@Override
public void stop() {
if (!isStarted())
return;
waitForAsynchronousJobToStop(compressionFuture,"compression");
waitForAsynchronousJobToStop(cleanUpFuture, "clean-up");
super.stop();
}
private void waitForAsynchronousJobToStop(Future<?> aFuture, String jobDescription) {
if (aFuture != null) {
try {
// 等待指定時(shí)間,如果任務(wù)還未執(zhí)行完,就拋出異常
aFuture.get(CoreConstants.SECONDS_TO_WAIT_FOR_COMPRESSION_JOBS, TimeUnit.SECONDS);
} catch (TimeoutException e) {
addError("Timeout while waiting for " + jobDescription + " job to finish", e);
} catch (Exception e) {
addError("Unexpected exception while waiting for " + jobDescription + " job to finish", e);
}
}
}
在日志系統(tǒng)停止時(shí),如果壓縮操作還在進(jìn)行,等待一定時(shí)間,超過該時(shí)間拋出異常。