Logback與Log4j2日志框架性能對比與調(diào)優(yōu)方式
前言
看到目前線上大多日志框架測評大多從宏觀角度,直接對比異步同步的吞吐量,但是沒有考量到更深層的淘汰機制、等待策略、隊列長度等對性能表現(xiàn)的影響,因此本文將從更多的角度對比及分析兩款日志框架的性能表現(xiàn),通過JProfiler+Jmeter壓測及數(shù)據(jù)采集,從線程占用、鎖占用、宏觀耗時等多維度可視化數(shù)據(jù)。
性能測試
logback
同步日志
耗時
未經(jīng)過任何調(diào)優(yōu),采用Logback默認配置得出上圖,一百萬條日志打印耗時(ms),如圖:單線程下性能最佳,耗時隨線程數(shù)增加而下降。
線程占用
單線程
無阻塞狀態(tài)
多線程
多線程打印日志時,會產(chǎn)生大量線程阻塞,線程越多阻塞狀態(tài)越多
四線程
八線程
十六線程
鎖占用
線程發(fā)生多次占用鎖的情況。查看Logback源碼可得知,檢查容量、放入隊列、取出隊列都需要在取得鎖后進行
異步日志(隊列擴容)
樣本數(shù)100萬,隊列長度110萬
耗時
線程占用
單線程
多線程
四線程
八線程
十六線程
鎖占用
每次寫入隊列都需要占用鎖,同時Appender從隊列取出也需要占用鎖
異步日志(半隊列擴容)
樣本數(shù)100萬,隊列長度50萬,不啟用拋棄策略
耗時
線程占用
單線程
多線程
寫入耗時明顯增長,寫入過程仍然發(fā)生阻塞狀態(tài)
四線程
八線程
十六線程
鎖占用
log4j2
同步日志
樣本數(shù)100萬,Logger到Appender串行執(zhí)行,輸出到文件
耗時
線程占用
線程產(chǎn)生長時間的等待,主要是緩沖環(huán)溢出后無法寫入,生產(chǎn)者根據(jù)等待策略進入等待狀態(tài)
單線程
單線程生產(chǎn)不需要爭搶鎖,因此全程無阻塞
多線程
整體來看,阻塞的時間隨著線程增多而增多,因此多線程對同步日志影響極大,性能損失嚴重
四線程
八線程
十六線程
后續(xù)監(jiān)控因阻塞時間太長跳過
鎖占用
阻塞在Appender上的輸出流上,輸出流是在單線程中執(zhí)行的
異步日志(隊列擴容)
樣本數(shù)100萬,隊列長度110萬,使用Yield等待策略
耗時
單線程占用最高,耗時隨線程數(shù)增加而縮短,直到線程數(shù)超過CPU核數(shù)。單線程耗時與logback相當,多線程耗時比logback縮短了2倍
線程占用
單線程與多線程使用都無阻塞狀態(tài),保證足夠的隊列容量,能使日志操作保持高吞吐和低延遲,避免阻塞等待
單線程
多線程
四線程
八線程
使用與宿主機CPU核數(shù)相等的線程數(shù),日志寫入過程無阻塞、無線程切換
十六線程
異步日志(日志淘汰策略)
樣本數(shù)100萬,隊列長度50萬,啟用拋棄策略
耗時
線程占用
隊列長度50萬,正常來說應(yīng)與半隊列擴容一樣,產(chǎn)生阻塞現(xiàn)象,但啟用了日志淘汰策略,無法寫入隊列的將直接拋棄不阻塞等待
單線程
多線程
四線程
八線程
十六線程
異步日志(半隊列擴容)
樣本數(shù)100萬,隊列長度50萬,使用Yield等待策略
耗時
當隊列滿后,大幅影響了響應(yīng)時間,吞吐量依賴Appender的消費性能
線程占用
單線程記錄日志時,前半段隊列未滿時生產(chǎn)線程一直處于工作狀態(tài),后半段因消費能力跟不上生產(chǎn)能力,導(dǎo)致隊列滿載,生產(chǎn)線程開始出現(xiàn)等待狀態(tài)
單線程
等待的時間比多線程少,是因為單線程下日志生產(chǎn)速度慢,同時日志也在倍消費
多線程
前一段時間可以維持高性能工作,但后面隊列滿后開始發(fā)送等待,導(dǎo)致耗時延長
四線程
八線程
十六線程
鎖占用
并未發(fā)現(xiàn)日志記錄過程中發(fā)生鎖占用
異步日志(等待策略)
樣本數(shù)100萬,隊列長度50萬,使用Timeout等待策略
耗時
線程占用
未產(chǎn)生阻塞狀態(tài)
單線程
多線程
因Timeout等待策略使用了鎖,因此產(chǎn)生一定的阻塞
四線程
八線程
十六線程
鎖占用
使用Timeout等待策略時,放入隊列前會取鎖,進行消費者線程喚醒動作
性能調(diào)優(yōu)
異步日志
無論是logback還是log4j2,使用異步日志可以大幅提高日志操作耗時,間接提高業(yè)務(wù)方整體耗時
日志可靠性
異步日志無法保證日志可靠性,系統(tǒng)意外關(guān)閉會丟失隊列中的日志,因此要求高可靠的日志,應(yīng)該選擇數(shù)據(jù)庫或者MQ來保證
Logback
通過
<appender name="async-log-all" class="ch.qos.logback.classic.AsyncAppender">
設(shè)置
Log4j2
通過
System.setProperty("Log4jContextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector")
設(shè)置
日志拋棄策略
將溢出隊列的日志拋棄,保持穩(wěn)定的響應(yīng)速度。對業(yè)務(wù)方來說能保持良好、穩(wěn)定的日志服務(wù),但需要容忍一定的日志丟
Log4j2
通過
System.setProperty("log4j2.AsyncQueueFullPolicy","Discard")
設(shè)置
Logback
通過<discardingThreshold>指定拋棄日志的閾值
拋棄邊界
當隊列剩余容量小于閾值后,將拋棄ERROR以下的日志
禁用拋棄策略
設(shè)置為0則表示不拋棄,業(yè)務(wù)線程等待隊列空間可用后寫入
默認閾值
默認閾值為隊列長度的20%,隊列長度100閾值為20
日志等待策略
Log4j2獨有的特性,指定隊列滿時,生產(chǎn)者進行等待的行為,需要在不開啟拋棄策略下進行
TimeoutWaitStrategy
Log4j2默認的等待策略,通過Object.wait等待隊列騰空。在放入隊列時會加鎖,不推薦使用。
YieldWaitStrategy
通過
System.setProperty("log4j2.asyncLoggerWaitStrategy","Yield")
設(shè)置。通過System.yield()等待隊列騰空,比Timeout等待策略更高效,比Busy等待策略更節(jié)能
隊列容量
由性能測試可知,不適用日志拋棄策略下,隊列滿載后生產(chǎn)線程將阻塞等待隊列騰空,直接影響業(yè)務(wù)方的效率
Logback
通過<queueSize>指定隊列長度,Logback固定使用ArrayBlockingQueue作為隊列
Log4j2
通過
System.setProperty("log4j2.asyncLoggerRingBufferSize","x")
指定
二次方長度
RingBuffer內(nèi)部計算位置時通過二進制方式計算,使用二的指數(shù)長度可以提高計算速度
長度計算公式
暫未找到統(tǒng)一標準的計算公式,本人覺得可以通過(日志峰值TPS#消費TPS)*15*60來計算
承載容量
這個公式的含義是:應(yīng)用15分鐘以峰值去生產(chǎn)的日志可以全部被隊列容納
成本
從成本的角度看,隊列不應(yīng)該無限量地預(yù)估,在保證系統(tǒng)不受到容量影響下,盡可能地使用小的長度,節(jié)省內(nèi)存開支
響應(yīng)時間
一般應(yīng)用不應(yīng)該長時間在峰值運行,如果出現(xiàn)長時間在峰值運行,則應(yīng)該進行水平拓展分散請求壓力。因此容納15分鐘之內(nèi)的峰值,可以有足夠時間讓運維響應(yīng),進行水平拓展分散壓力。
消費瓶頸
日志消費TPS由Appender消費效率決定,當日志TPS超過消費TPS時,日志將開始在隊列中堆積
消費TPS
某個Appender在一秒內(nèi)消費的日志數(shù)量,舉FileAppender為例,每條日志消費花費100微妙(性能好的主機可以到60),一秒可以消費1萬條日志,即消費TPS為1萬
請求TPS
一般Web應(yīng)用不會承載超過消費TPS的流量。假設(shè)每個請求打印五條日志,則需要5000以上的TPS才能產(chǎn)生日志堆積
消費者優(yōu)化
日志TPS長時間(15min+)超過消費TPS的場景下,應(yīng)該對消費能力進行優(yōu)化,使用輕量級的Appedner、簡單的Layout、日志拋棄策略、過濾器、業(yè)務(wù)方規(guī)范等方面進行優(yōu)化
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
IntelliJ Idea 2020.1 正式發(fā)布,官方支持中文(必看)
這篇文章主要介紹了IntelliJ Idea 2020.1 正式發(fā)布,官方支持中文了,本文通過截圖的形式給大家展示,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04spring boot activiti工作流的搭建與簡單使用
這篇文章主要給大家介紹了關(guān)于spring boot activiti工作流的搭建與簡單使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08Java中的String、StringBuilder、StringBuffer三者的區(qū)別詳解
這篇文章主要介紹了Java中的String、StringBuilder、StringBuffer三者的區(qū)別詳解,就是String,StringBuilder以及StringBuffer這三個類之間有什么區(qū)別呢,自己從網(wǎng)上搜索了一些資料,有所了解了之后在這里整理一下,便于大家觀看,需要的朋友可以參考下2023-12-12SpringBoot選擇自有bean優(yōu)先加載實現(xiàn)方法
在一些需求中,可能存在某些場景,比如先加載自己的bean,然后自己的bean做一些DB操作,初始化配置問題,然后后面的bean基于這個配置文件,繼續(xù)做其他的業(yè)務(wù)邏輯。因此有了本文的這個題目2023-03-03Java Optional解決空指針異??偨Y(jié)(java 8 功能)
這篇文章主要介紹了Java Optional解決空指針異??偨Y(jié)(java 8 功能),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11