兩年前,我在一家傳統金融軟體公司負責為某交易所開發大型交易系統。該系統處理流式價格數據,採用價格觸發成交模式,支援 T+0 即時開平倉。鑑於系統需要記錄海量價格數據、交易資訊與訂單流水,且對效能極其敏感(毫秒級),必須避免日誌服務成為效能瓶頸。經過對 log4j、logback 等通用日誌框架的壓測,發現它們因通用性導致體積較大、開銷較高,於是決定自行開發一款輕量級日誌工具——最初僅作為專案中的一個 util 類,代碼僅幾百行。
系統上線兩個月後,日均成交額達 200 億人民幣,峰值 440 億,峰值成交 4000 筆/秒。龐大的系統架構下,這幾百行代碼完美支撐了關鍵日誌服務。基於此優異表現,我將其抽離為獨立組件 FLogger,幾乎未修改原始邏輯,現已開源至 GitHub,歡迎有興趣的開發者研究與改進。
FLogger 設計 API 與 log4j 高度相似,降低遷移成本:
// 獲取單例
FLogger logger = FLogger.getInstance();
// 簡便 API
logger.info("Here is your message...");
// 指定級別(文件名自動映射)
logger.writeLog(Constant.INFO, "Custom level message...");
// 指定文件名、級別、內容
logger.writeLog("error", Constant.ERROR, "Custom file & level message...");
專案根目錄下建立 log.properties 即可配置(所有項目均有預設值,缺檔不拋異常):
########## 公共環境配置 ##########
CHARSET_NAME = UTF-8
########## 日誌信息配置 ##########
LOG_LEVEL = 0,1,2,3,4
LOG_PATH = ./log
WRITE_LOG_INV_TIME = 1000
SINGLE_LOG_FILE_SIZE = 10485760
SINGLE_LOG_CACHE_SIZE = 10240
輸出格式規範統一,易於閱讀與排查:
[INFO] 2016-12-06 21:07:32:840 [main] Here is your message...
[WARN] 2016-12-06 21:07:32:842 [main] Here is your customized level message...
[ERROR] 2016-12-06 21:07:32:842 [main] Here is your customized log file and level message...
格式結構:[日誌級別] 精確到毫秒的時間 [當前線程名] 日誌內容。為保持純粹,目前不支援自定義格式。
每個日誌文件對應一個 LogFileItem,內含兩個 ArrayList<StringBuffer>(A/B 緩衝區)。寫入時向當前緩衝區追加;刷盤時交換引用,將滿緩衝區移交寫入線程,空緩衝區繼續接收新日誌,實現無鎖高併發寫入。
// 寫入邏輯(同步單文件)
synchronized(lfi) {
if (lfi.currLogBuff == 'A') lfi.alLogBufA.add(logMsg);
else lfi.alLogBufB.add(logMsg);
lfi.currCacheSize += CommUtil.StringToBytes(logMsg.toString()).length;
}
// 刷盤邏輯:交換緩衝區引用
ArrayList<StringBuffer> alWrtLog = null;
synchronized(lfi) {
if (lfi.currLogBuff == 'A') {
alWrtLog = lfi.alLogBufA; lfi.currLogBuff = 'B';
} else {
alWrtLog = lfi.alLogBufB; lfi.currLogBuff = 'A';
}
lfi.currCacheSize = 0;
}
createLogFile(lfi);
int iWriteSize = writeToFile(lfi.fullLogFileName, alWrtLog);
lfi.currLogSize += iWriteSize;
WRITE_LOG_INV_TIME(預設 1000ms)SINGLE_LOG_CACHE_SIZE(預設 10KB)ShutdownHook,正常關閉時保證不丟日誌核心刷盤循環每 200ms 執行一次,每 100 次迴圈熱加載一次配置(可調),以極低開銷實現動態調整。
SINGLE_LOG_FILE_SIZE(預設 10MB)重命名為 info_20161208_011105.loginfo.log// 關鍵切割邏輯
if (lfi.currLogSize >= LogManager.SINGLE_LOG_FILE_SIZE) {
File oldFile = new File(lfi.fullLogFileName);
String newFileName = Constant.CFG_LOG_PATH + "/" + lfi.lastPCDate + "/"
+ lfi.logFileName + "_" + TimeUtil.getPCDate() + "_" + TimeUtil.getCurrTime() + ".log";
oldFile.renameTo(new File(newFileName));
lfi.fullLogFileName = ""; lfi.currLogSize = 0;
}
// 按天建立新文件
if (lfi.fullLogFileName == null || !lfi.lastPCDate.equals(currPCDate)) {
String sDir = Constant.CFG_LOG_PATH + "/" + currPCDate;
new File(sDir).mkdir();
lfi.fullLogFileName = sDir + "/" + lfi.logFileName + ".log";
lfi.lastPCDate = currPCDate;
}
環境:i5-3470 3.2GHz、4GB RAM、64位 OS
場景:單條 72 字節、100 萬條、寫單文件、配置極致實時(緩衝 0、間隔 0)
結果(10 次平均):
FLogger 快約 5.9 倍。註:非嚴格基準測試,僅供參考,歡迎進一步嚴謹對比。
FLogger 證明了「小而美」在極致效能場景下的價值。幾百行純 Java 代碼,無依賴、易嵌入、經百億級實戰驗證。若您的系統對日誌延遲極度敏感,不妨嘗試 FLogger,或從中汲取雙緩衝、零依賴設計靈感。
