一区二区三区电影_国产伦精品一区二区三区视频免费_亚洲欧美国产精品va在线观看_国产精品一二三四

聯系我們 - 廣告服務 - 聯系電話:
您的當前位置: > 關注 > > 正文

Log4j2源碼解析:同步寫、異步寫原理、中間技術思考

來源:CSDN 時間:2023-03-07 11:41:12

Log4j2中的組件 從配置開始API基本使用小細節 寫日志的原理主要流程 同步寫異步寫 順便說一下ArrayBlockingQueue notFull 與 notEmpty 異步寫是怎么玩的巧妙的異步寫設計 ByteBuffer與RandomAccessFileGarbage-free避免創建多余對象異步Logger性能 寫在最后

本文主要記錄我對Log4j2源碼閱讀后的一些個人理解,包括的內容有:Log4j2的組件、同步寫、異步寫原理、以及中間技術的個人思考。而log4j2的使用與配置稍顯簡易,可在很多地方找到說明,本文則不重點討論這部分的內容。


【資料圖】

Log4j2中的組件

Log4j2以插件的方式來配置各個組件,在配置中可自由的插拔組件,并支持自動生效(配置monitorInterval)。

從配置開始

Log4j2支持多種格式配置文件,xml、json、yaml、properties。相應地,初始化時會逐個查找并加載這類文件。以最常用的xml配置為例,Log4j2默認的文件名稱為log4j2.xml,主要配置如下:

target/rolling.log

如上配置的對象有: 1. Configuration: 表示日志環境的一份配置描述,用于構建一份運行時的LoggerContext2. Logger表示日志記錄器,可有多個。示例中有2個普通記錄器,1個根記錄器。一個記錄器需要指定Appender,可指定多個,表示日志記錄到哪里。 3. Appenders表示日志的輸出源,可有多個。示例中有3個,Console表示通過標準輸出流輸出到控制臺,RollingRandomAccessFile表示可滾動的記錄日志,日志文件到達一定的大小后,會啟用壓縮。 Async表示一種異步的輸出端,通過異步線程與隊列的方式寫入日志到其關聯的實際Appender中。

API基本使用

// 獲取Logger private static final Logger logger = LogManager.getLogger("HelloWorld"); private static final Logger logger = LogManager.getLogger(Class.getName()); logger.info("Hello, World!");logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthdayCalendar());logger.debug("Logging in user %s with birthday %s", user.getName(), user.getBirthdayCalendar());

小細節

Log4j2在對外的info,warn等API上在內置了logIfEnabled的判斷,而不用在程序中顯示的寫上:

if (logger.isInfoEnabled()) {    logger.info();}if (logger.isWarnEnabled()) {    logger.warn();}

所以在寫日志時,放心的直接用logger.info,logger.warn吧。

寫日志的原理

主要流程

同步寫

如上流程是典型的同步寫的過程,多線程并發寫是通過臨界區來實現。簡要過程如下:

相關代碼: AbstractOutputStreamAppender#directEncodeEvent 以常用的基于PatternLayout的日志格式為例:

private void encodeSynchronized(final CharsetEncoder charsetEncoder, final CharBuffer charBuffer,            final StringBuilder source, final ByteBufferDestination destination) {      // 臨界區阻塞式Encode        synchronized (destination) {            try {                TextEncoderHelper.encodeText(charsetEncoder, charBuffer, destination.getByteBuffer(), source,                        destination);            } catch (final Exception ex) {               ..            }        }    }// 根據不同的Appender用OutputStream.writeBytespublic synchronized void flush() {        flushBuffer(byteBuffer);        flushDestination();}

異步寫

如上配置的Async表示一種異步輸出器,對應的實現為AsyncAppender。 異步Appender使用異步線程加阻塞隊列(BlockingQueue)來實現異步寫的功能。默認情況下通過BlockingQueueFactory創建缺省的隊列類型為:ArrayBlockingQueue,隊列大小為128.

順便說一下ArrayBlockingQueue

notFull 與 notEmpty

put和take阻塞調用線程是借用notFull和notEmpty兩個條件對象來實現的。

所以在理解這兩個詞的意思時,看看Doug Lea給的注釋:

等待take的條件    private final Condition notEmpty;    等待put的條件    private final Condition notFull;

換言之,此處的notEmpty表示為:只有隊列在notEmpty的條件下才能take, 如果隊列為empty,那么當前take的線程則需要等待。類似的,當隊列在notFull時,才能put, 如果隊列為full, 那么當前put的線程則需要等待。

異步寫是怎么玩的

業務線程并發調用同一個Logger寫日志時,Log4j2內部把內容解析成LogEvent,然后投遞到隊列中,由異步的線程來負責消費。

相關代碼: 投遞到隊列:

public void append(final LogEvent logEvent) {         ...         獲取不可變的副本對象        final Log4jLogEvent memento = Log4jLogEvent.createMemento(logEvent, includeLocation);        if (!transfer(memento)) { // 投遞到隊列失敗,降級策略            if (blocking) { // 新版本默認為true                // 根據策略不同,可丟棄、可等待、可由當前線程執行                final EventRoute route = asyncQueueFullPolicy.getRoute(thread.getId(), memento.getLevel());                route.logMessage(this, memento);            } else {                // 交由error-appender處理                logToErrorAppenderIfNecessary(false, memento);            }        }    }// 投遞LogEvent到隊列,新版本可指定隊列為LinkedTransferQueueprivate boolean transfer(final LogEvent memento) {        return queue instanceof TransferQueue            ? ((TransferQueue) queue).tryTransfer(memento)            : queue.offer(memento);}

消費寫LogEvent

消費while (!queue.isEmpty()) {     try {           final LogEvent event = queue.take();           if (event instanceof Log4jLogEvent) {               final Log4jLogEvent logEvent = (Log4jLogEvent) event;               logEvent.setEndOfBatch(queue.isEmpty());               // 在異步線程中調用Appender寫日志               callAppenders(logEvent);           } else {               ...          }       } catch (final InterruptedException ex) {           不處理異常,繼續寫日志       } }

巧妙的異步寫設計

實際環境中,很多業務線程在并發的寫日志到隊列中,并由一條異步消費者線程負責消費寫。高并發的場景下,很容易造成生成的速度大于消費的速度。 為了不阻塞生成,投遞LogEvent時選擇的是隊列的offer方法,如果成功入隊,則執行馬上返回給應用。如果隊列滿了,則默認降級為在同步寫,這種設計能極大的提供性能 。而消費時選擇的隊列的take方法,如果生產的日志較少,則park消費者線程,讓出CPU。 注意到offer方法在入隊成功時,也會調用notEmpty.signal()方法,進而喚醒消費者,從而讓它繼續工作。

ByteBuffer與RandomAccessFile

項目中常常會用到RollingRandomAccessFile這種Appender,而在Log4j2中都是基于ByteBuffer來管理字節緩沖,并用于處理字節流與字符流的高效轉換。最后采用RandomAccessFile來寫Bytes到文件。Log4j2官方給出的性能測試報告提到相較于BufferedOutputStream,ByteBuffer + RandomAccessFile有20-200%的性能改善。

private ByteBuffer getByteBuffer() {        ByteBuffer result = byteBufferThreadLocal.get();        if (result == null) {            result = ByteBuffer.wrap(new byte[byteBufferSize]);            byteBufferThreadLocal.set(result);        }        return result;}

在輸出ByteBuffer時,根據Appender的不同選擇不同的輸出策略?;赗andomAccessFile的輸出為: randomAccessFile.write(byteBuffer.toArray(), 0, byteBuffer.limit());

Garbage-free(避免創建多余對象)

Log4j2提倡創建最少的對象做更多事,盡量避免創建多余的對象。在內部有很多細節代碼,這里分析2個例子。 1. API設計上避免創建變長數組 用void info(String message, Object p0, Object p1, Object p2, Object p3)這類參數明確的api替換帶變長數組的api.

提供工具來避免基礎類型參數的自動裝箱. 自動裝箱會創建大量的對象,Log4j2提供Unbox工具類來轉換為StringBuilder.

異步Logger

事實上log4j2的異步logger才是性能改善最卓越的部分。異步Logger內部采用 Disruptor來實現,它是一個用于代替隊列的無鎖化線程間的通信的工具庫,可以明顯的提高吞吐量并降低延遲。 自己后續會對Disruptor做一個學習與分析。感謝關注。

性能

Log4j2的性能改善是明顯的,官方文檔:http://logging.apache.org/log4j/2.x/performance.html 提供了大量的場景對比結果,有興趣的可以去了解一下。

寫在最后

The log4j1.x amost has became End of Life。So upgrade it and learn about it.

哦,對了,升級的時候可以用SLF4J來做統一的外觀,然后引入log4j-api,log4j-core, slf4j-api,還有slf4j到log4j2的橋接器log4j-slf4j-impl就行。但是需要注意的是: 咱們內部中間件大多默認依賴了slf4j-log4j12,因此需要把這類依賴全部排出, 可以用如下的tip:

org.slf4jslf4j-log4j12999-not-exist

Good luck. Tks.

責任編輯:

標簽:

相關推薦:

精彩放送:

新聞聚焦
Top 主站蜘蛛池模板: 梧州市| 济南市| 措勤县| 三门峡市| 新邵县| 苍溪县| 开封县| 轮台县| 长葛市| 长汀县| 泰和县| 含山县| 综艺| 海兴县| 临海市| 益阳市| 剑川县| 休宁县| 孟连| 南丰县| 繁昌县| 海南省| 桦南县| 广水市| 温宿县| 赞皇县| 秦皇岛市| 昌宁县| 西昌市| 周至县| 固阳县| 栾川县| 瓦房店市| 峡江县| 阳原县| 宁夏| 汨罗市| 石景山区| 澎湖县| 景德镇市| 罗城|