背景
在最近的一次項目性能優化過程中,通過火焰圖工具發現logback占用CPU很多,因此有了這篇總結文章。
logback 同步 vs 異步
同步寫日志一般配置如下:
1 |
<appender name="ORDER_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
從配置可以看出來,寫日志到日志文件的操作由RollingFileAppender
完成,該類的繼承結構如下:
從繼承結構可以知道RollingFileAppender
繼承了UnsynchronizedAppenderBase
,根據doc文檔說明,RollingFileAppender
需要自己處理多線程同步的問題。 在內部它確實也自己做了同步。
RollingFileAppender
1 |
@Override |
OutputStreamAppender.java
OutputStreamAppender.java
1 |
/** |
LayoutWrappingEncoder.java
1 |
LayoutWrappingEncoder.java |
通過上面的代碼邏輯可以得出如下結論:
- RollingFileAppender 寫日志自己實現了多線程同步。
- RollingFileAppender 寫日志是直接接入到日志文件中的。
- RollingFileAppender 默認日志到文件后會立刻flush,保證日志不丟失。
- 因為是順序寫文件,速度還是很高的,但是應為每次都flush,這會影響性能。
為了防止寫日志影響應用性能, 我們需要使用異步寫日志的方式。
異步寫日志一般配置如下:
1 |
<appender name ="ASYNC_ORDER_LOG" class= "ch.qos.logback.classic.AsyncAppender"> |
AsyncAppender
繼承自 AsyncAppenderBase
。 AsyncAppenderBase
的doc文檔有如下的描述:
AsyncAppenderBase的子類寫日志是異步的方式,內部使用了 BlockingQueue(異步寫日志就是一個生產者-消費者模式)。
BlockingQueue 的使用者負責在應用關閉時關閉BlockingQueue,來確保不丟失日志。
注意:不丟失日志,可以通過如下的方式實現:
1 |
<configuration debug="true"> |
或
1 |
Runtime.addShutdownHook(new Thread (() -> { |
AsyncAppenderBase
代碼分析:
1 |
public static final int DEFAULT_QUEUE_SIZE = 256; |
小結
- 同步寫日志而且immediateFlush=true的配置下,性能最差。原因是寫磁盤導致其他線程等待時間過長,雖然是順序寫,但是畢竟是持久化數據到磁盤。當然了,SSD能好一點。
- 異步寫在JVM突然crash的時候有丟失數據的風險,但是性能很高,原因在於避免了直接寫磁盤帶來的性能消耗。但是需要注意的是多線程操作同一個阻塞隊列也會因為鎖爭用的問題影響性能。
a. 不同的模塊配置不同的日志文件和Appender,能減少鎖爭用的問題。
b. 減少不必要的日志輸出。
c. 增加阻塞隊列的大小,在neverBlock=false
的情況下避免線程等待問題。
d. 多個Appender(SiftingAppender),底層還是寫同一個文件。好處是減少了多線程在阻塞隊列上的鎖競爭問題。
SiftingAppender
SiftingAppender是logback根據mdc中的變量動態創建appender的代理,只要我們將一個線程號作為日志名分發器discriminator注入到SiftingAppender中,它就可以動態的為我們創建不同的appender,達到分線程的目的,配置方式舉例如下:
<!-- 分線程輸出源 --> <appender name="frameworkthread" class="ch.qos.logback.classic.sift.SiftingAppender"> <discriminator class="ThreadDiscriminator"> <key>threadName</key> </discriminator> <sift> <appender name="FILE-${threadName}" class="ch.qos.logback.core.rolling.RollingFileAppender"> <encoder> <Encoding>UTF-8</Encoding> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}[%c][%thread][%X{tradeNo}][%p]-%m%n</pattern> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>D:/test/threadlogs/${threadName}-%d{yyyy-MM-dd}.%i.log </fileNamePattern> <maxFileSize>100MB</maxFileSize> <maxHistory>60</maxHistory> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> </appender> </sift> </appender>
參考資料
http://logback.qos.ch/manual/appenders.html
一次logback多線程調優的經歷
原文鏈接:https://leokongwq.github.io/2019/12/14/logback-best-practise.html