1. 程式人生 > >異步日誌

異步日誌

開啟 dde rar ebp 靈活 ces end 技術分享 不能

[z]https://www.jianshu.com/p/9f0c67facbe2

簡介

Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor, Log4j 1.x, and provides many of the improvements available in Logback while fixing some inherent problems in Logback’s architecture.

log4j2是log4j 1.x 的升級版,參考了logback的一些優秀的設計,並且修復了一些問題,因此帶來了一些重大的提升,主要有:

技術分享圖片 主要特點
  • 異常處理,在logback中,Appender中的異常不會被應用感知到,但是在log4j2中,提供了一些異常處理機制。

  • 性能提升, log4j2相較於log4j 1和logback都具有很明顯的性能提升,後面會有官方測試的數據。

  • 自動重載配置,參考了logback的設計,當然會提供自動刷新參數配置,最實用的就是我們在生產上可以動態的修改日誌的級別而不需要重啟應用——那對監控來說,是非常敏感的。

  • 無垃圾機制,log4j2在大部分情況下,都可以使用其設計的一套無垃圾機制,避免頻繁的日誌收集導致的jvm gc。

一些概念

之前看官方文檔摘抄了一些概念,這裏懶得翻譯了,使用log4j的都應該清楚,這裏只是mark下。

技術分享圖片 log4j2 中的一些概念

舉個栗子

<Configuration status="debug" packages="org.apache.logging.log4j.test">
  <Properties>
    <Property name="filename">target/test.log</Property>
  </Properties>
  <Filter type="ThresholdFilter" level="trace"/>
  <Appenders>
    <Appender type="Console" name="STDOUT">
      <Layout type="PatternLayout" pattern="%m MDC%X%n"/>
      <Filters>
        <Filter type="MarkerFilter" marker="FLOW" onMatch="DENY" onMismatch="NEUTRAL"/>
        <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="DENY" onMismatch="ACCEPT"/>
      </Filters>
    </Appender>
    <Appender type="Console" name="FLOW">
      <Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/><!-- class and line number -->
      <Filters>
        <Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
        <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
      </Filters>
    </Appender>
    <Appender type="File" name="File" fileName="${filename}">
      <Layout type="PatternLayout">
        <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
      </Layout>
    </Appender>
  </Appenders>
 
  <Loggers>
    <Logger name="org.apache.logging.log4j.test1" level="debug" additivity="false">
      <Filter type="ThreadContextMapFilter">
        <KeyValuePair key="test" value="123"/>
      </Filter>
      <AppenderRef ref="STDOUT"/>
    </Logger>
    <Logger name="org.apache.logging.log4j.test2" level="debug" additivity="false">
      <AppenderRef ref="File"/>
    </Logger>
    <Root level="trace">
      <AppenderRef ref="STDOUT"/>
    </Root>
  </Loggers>
</Configuration>

異步日誌

log4j2最大的特點就是異步日誌,其性能的提升主要也是從異步日誌中受益,我們來看看如何使用log4j2的異步日誌。

Log4j2提供了兩種實現日誌的方式,一個是通過AsyncAppender,一個是通過AsyncLogger,分別對應前面我們說的Appender組件和Logger組件。註意這是兩種不同的實現方式,在設計和源碼上都是不同的體現。

AsyncAppender方式

The AsyncAppender accepts references to other Appenders and causes LogEvents to be written to them on a separate Thread. Note that exceptions while writing to those Appenders will be hidden from the application. The AsyncAppender should be configured after the appenders it references to allow it to shut down properly.
By default, AsyncAppender uses java.util.concurrent.ArrayBlockingQueue which does not require any external libraries. Note that multi-threaded applications should exercise care when using this appender as such: the blocking queue is susceptible to lock contention and our tests showed performance may become worse when more threads are logging concurrently. Consider using lock-free Async Loggers for optimal performance.

AsyncAppender是通過引用別的Appender來實現的,當有日誌事件到達時,會開啟另外一個線程來處理它們。需要註意的是,如果在Appender的時候出現異常,對應用來說是無法感知的。 AsyncAppender應該在它引用的Appender之後配置,默認使用 java.util.concurrent.ArrayBlockingQueue實現而不需要其它外部的類庫。 當使用此Appender的時候,在多線程的環境下需要註意,阻塞隊列容易受到鎖爭用的影響,這可能會對性能產生影響。這時候,我們應該考慮使用無所的異步記錄器(AsyncLogger)。

舉個栗子

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <File name="MyFile" fileName="logs/app.log">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
    </File>
    <Async name="Async">
      <AppenderRef ref="MyFile"/>
    </Async>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="Async"/>
    </Root>
  </Loggers>
</Configuration>

AsyncAppender有一些配置項,如下:

技術分享圖片 image.png

除此之外還有一些其他的細節,如果感興趣可以參考官網文檔,這裏就不一一列舉了。

AsyncLogger方式

AsyncLogger才是log4j2 的重頭戲,也是官方推薦的異步方式。它可以使得調用Logger.log返回的更快。你可以有兩種選擇:全局異步和混合異步。

  • 全局異步就是,所有的日誌都異步的記錄,在配置文件上不用做任何改動,只需要在jvm啟動的時候增加一個參數;

  • 混合異步就是,你可以在應用中同時使用同步日誌和異步日誌,這使得日誌的配置方式更加靈活。因為Log4j文檔中也說了,雖然Log4j2提供以一套異常處理機制,可以覆蓋大部分的狀態,但是還是會有一小部分的特殊情況是無法完全處理的,比如我們如果是記錄審計日誌,那麽官方就推薦使用同步日誌的方式,而對於其他的一些僅僅是記錄一個程序日誌的地方,使用異步日誌將大幅提升性能,減少對應用本身的影響。混合異步的方式需要通過修改配置文件來實現,使用AsyncLogger標記配置。

舉個栗子

全局異步

配置文件不用動:

<?xml version="1.0" encoding="UTF-8"?>
 
<!-- Don‘t forget to set system property
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
     to make all loggers asynchronous. -->
 
<Configuration status="WARN">
  <Appenders>
    <!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
    <RandomAccessFile name="RandomAccessFile" fileName="async.log" immediateFlush="false" append="false">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m %ex%n</Pattern>
      </PatternLayout>
    </RandomAccessFile>
  </Appenders>
  <Loggers>
    <Root level="info" includeLocation="false">
      <AppenderRef ref="RandomAccessFile"/>
    </Root>
  </Loggers>
</Configuration>

在系統初始化的時候,增加全局參數配置:

System.setProperty("log4j2.contextSelector, "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");

你可以在你第一次獲取Logger之前設置,也可以加載JVM啟動參數裏,類似

java -Dog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

混合異步

混合異步只需要修改配置文件即可:

<?xml version="1.0" encoding="UTF-8"?>
 
<!-- No need to set system property "log4j2.contextSelector" to any value
     when using <asyncLogger> or <asyncRoot>. -->
 
<Configuration status="WARN">
  <Appenders>
    <!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
    <RandomAccessFile name="RandomAccessFile" fileName="asyncWithLocation.log"
              immediateFlush="false" append="false">
      <PatternLayout>
        <Pattern>%d %p %class{1.} [%t] %location %m %ex%n</Pattern>
      </PatternLayout>
    </RandomAccessFile>
  </Appenders>
  <Loggers>
    <!-- pattern layout actually uses location, so we need to include it -->
    <AsyncLogger name="com.foo.Bar" level="trace" includeLocation="true">
      <AppenderRef ref="RandomAccessFile"/>
    </AsyncLogger>
    <Root level="info" includeLocation="true">
      <AppenderRef ref="RandomAccessFile"/>
    </Root>
  </Loggers>
</Configuration>

在上面示例的配置中,root logger就是同步的,但是com.foo.Bar的logger就是異步的。

使用Log4j日誌的註意事項

在使用異步日誌的時候需要註意一些事項,如下:

  1. 不要同時使用AsyncAppender和AsyncLogger,也就是在配置中不要在配置Appender的時候,使用Async標識的同時,又配置AsyncLogger,這不會報錯,但是對於性能提升沒有任何好處。

  2. 不要在開啟了全局同步的情況下,仍然使用AsyncAppender和AsyncLogger。這和上一條是同一個意思,也就是說,如果使用異步日誌,AsyncAppender、AsyncLogger和全局日誌,不要同時出現。

  3. 如果不是十分必須,不管是同步異步,都設置immediateFlush為false,這會對性能提升有很大幫助。

4、如果不是確實需要,不要打印location信息,比如HTML的location,或者pattern模式裏的%C or $class, %F or %file, %l or %location, %L or %line, %M or %method, 等,因為Log4j需要在打印日誌的時候做一次棧的快照才能獲取這些信息,這對於性能來說是個極大的損耗。

性能提升

關於性能測試,大家可以直奔官網,哪裏有很詳細的數據,這裏給個圖:

技術分享圖片 image.png 技術分享圖片 image.png

雖然我測下來,在immediateFlush設置為false的情況下,同步異步差不了多少,但可能是我的測試條件不符合官方的,從設計和原理上來說,異步日誌,無疑是個最優的選擇。

小結

總的來說,看了一遍log4j的官網文檔,對日誌系統有了個比較全面的了解,以前只是copy配置來改改,沒關註過很多細節,這次算是掃盲了一次。文章也只是做了個介紹,在實際使用中,還是要細細研究下配置。

另外,個人覺得異步模式無非就是在原來同步寫盤的前提下,增加消息隊列作為緩存,或者交個另一個線程去做,這理論上除了帶來一些額外的,較小的cpu和內存的開銷,應該會在高流量的時候帶來不小的性能提升,對比下來,log4j2無疑是當下最值得使用的日誌組件來,且可以使用其異步模式。

當然了,也不能說異步就一定好,如果日誌的流量不是特別大,磁盤性能又跟得上,沒有必要一定使用異步日誌。

異步日誌