首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

日交易額百億級交易系統的超輕量日誌實現:FLogger 設計與原理解析

     編輯:Java 綜合教學

前言:從百億級交易系統誕生的輕量日誌組件

兩年前,我在一家傳統金融軟體公司負責為某交易所開發大型交易系統。該系統處理流式價格數據,採用價格觸發成交模式,支援 T+0 即時開平倉。鑑於系統需要記錄海量價格數據、交易資訊與訂單流水,且對效能極其敏感(毫秒級),必須避免日誌服務成為效能瓶頸。經過對 log4j、logback 等通用日誌框架的壓測,發現它們因通用性導致體積較大、開銷較高,於是決定自行開發一款輕量級日誌工具——最初僅作為專案中的一個 util 類,代碼僅幾百行。

系統上線兩個月後,日均成交額達 200 億人民幣,峰值 440 億,峰值成交 4000 筆/秒。龐大的系統架構下,這幾百行代碼完美支撐了關鍵日誌服務。基於此優異表現,我將其抽離為獨立組件 FLogger,幾乎未修改原始邏輯,現已開源至 GitHub,歡迎有興趣的開發者研究與改進。

核心特性

  • 雙緩衝隊列:寫入與刷盤分離,消除「stop-the-world」效應
  • 多種刷盤機制:支援時間間隔、緩衝大小、服務關閉強制觸發
  • 多種 RollingFile 策略:按文件大小、按天自動切割歸檔
  • 完整日誌級別:debug、info、warn、error、fatal
  • 熱加載配置:無需重啟即可動態調整日誌級別等參數
  • 零第三方依賴:極致輕量,JAR 體積極小
  • 實戰驗證:已在日交易額百億級系統穩定運行

快速上手

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)
  • JVM 關閉鉤子強制刷盤:註冊 ShutdownHook,正常關閉時保證不丟日誌

核心刷盤循環每 200ms 執行一次,每 100 次迴圈熱加載一次配置(可調),以極低開銷實現動態調整。

RollingFile 自動切割

  • 按大小:超過 SINGLE_LOG_FILE_SIZE(預設 10MB)重命名為 info_20161208_011105.log
  • 按天:每天生成新目錄與文件,當前寫入始終為 info.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;
}

效能實測:FLogger vs log4j

環境:i5-3470 3.2GHz、4GB RAM、64位 OS
場景:單條 72 字節、100 萬條、寫單文件、配置極致實時(緩衝 0、間隔 0)
結果(10 次平均):

  • FLogger:2144 ms
  • log4j:12691 ms

FLogger 快約 5.9 倍。註:非嚴格基準測試,僅供參考,歡迎進一步嚴謹對比。

結語

FLogger 證明了「小而美」在極致效能場景下的價值。幾百行純 Java 代碼,無依賴、易嵌入、經百億級實戰驗證。若您的系統對日誌延遲極度敏感,不妨嘗試 FLogger,或從中汲取雙緩衝、零依賴設計靈感。

FLogger 架構示意圖

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved