1 問題のレビュー
1.1 問題の説明
プロジェクトのパフォーマンス テストでは、関連するインターフェイスの同時実行数が増加するにつれて、インターフェイスの応答時間が長くなり、インターフェイスのスループットが増加しなくなり、アプリケーションの CPU 使用率が高くなります。
1.2 分析の考え方
CPU 使用率の上昇を引き起こし、インターフェイス TPS の増加を妨げているのは誰ですか? インターフェイスの応答時間のコール チェーンの分布はどのようなものですか? 遅い点はありますか?
1) フレームグラフ解析を利用したアプリケーションのCPUは以下の通りですが、log4j2のログがCPUの約40%を占めており、当初はlog4j2の問題が疑われます。
2) コールチェーンの分析
pfinder で呼び出しチェーンを確認したところ、インターフェイスに合計 78 ミリ秒かかっており、明らかに遅いメソッド呼び出しや遅い SQL はなかったことがわかり、まずインターフェイス自体のコードの問題を解決しました。
1.3 暫定的な結論
log4j2 の問題については、ログ関連の構成 log4j2.xml を詳細に分析する必要があります。
上でわかるように、ログ内の Loggers ノードの下のノードとノードはすべて出力された同期ログです。ログの同期とは、プログラムのビジネスロジックとログ出力文を1つのスレッドで実行することを意味し、ログが多すぎると業務の効率をある程度阻害します。非同期ログに変更してみてください。
非同期ログ構成への変更: AsyncLogger を使用する
1.4 回帰検証
同期ログを非同期ログに変更した後。同じ 10 同時実行の場合、インターフェイスの TP99 は 51 ミリ秒から 23 ミリ秒に低下し、インターフェイスのスループット TPS は 493 から 1078 に増加し、アプリケーション CPU は 82% から 57% に低下しました。
完璧な終わり。問題は解決しましたが、log4j2 ログを詳しく調べる必要があります。
1.5 結論
- Log4j2 が非同期ログを使用すると、パフォーマンスが大幅に向上し、アプリケーション自体への影響が軽減されます。
- 不要なログ出力を根本的に削減します。
しかし、log4j2 非同期ログはどのように実装され、同期ログとの違いは何でしょうか? 非同期ロギングがより効率的になるのはなぜですか? これをきっかけに、log4j2 に関連する知識を学ぶようになりました。それを共有しましょう。
2 log4j2 ログ
2.1 log4j2 の利点
Log4j2 は log4j 1.x のアップグレード バージョンです。logback のいくつかの優れた設計を参照し、いくつかの問題を修正し、主に次のようないくつかの大きな改善をもたらします。
- 例外処理、logback では、Appender の例外はアプリケーションによって認識されませんが、log4j2 では、いくつかの例外処理メカニズムが提供されています。
- パフォーマンスの向上 log4j 1 や logback と比較すると、log4j2 は明らかにパフォーマンスが向上しています。後で正式なテスト データが公開される予定です。
- 自動リロード構成は、ログバックの設計を指します。もちろん、自動リフレッシュ パラメーター構成が提供されます。最も実用的なのは、アプリケーションを再起動せずに運用環境でログ レベルを動的に変更できることです。これは監視にとって非常に重要です。 。
- ガベージ メカニズムなし: ほとんどの場合、log4j2 は設計されたガベージ フリー メカニズムを使用して、頻繁なログ収集によって引き起こされる jvm gc を回避できます。
2.2 Log4J2 ログの分類
Log4j2 でログを記録するには、同期ログと非同期ログの 2 つの方法があります。非同期ログは、AsyncAppender を使用する方法と AsyncLogger を使用する方法の 2 つに分けられます。LOG4J2 を使用するロギング モードには、完全非同期ロギング、混合モード、同期ロギングの 3 つがあり、パフォーマンスは高いものから低いものまであり、スレッド数が多いほど効率が高く、ログ スタック スレッドの発生も回避できます。
同期ログと非同期ログのパフォーマンスの比較:
2.3 同期ログ
ログ出力にはlog4j2の同期ログを使用します ログ出力文とプログラムのビジネスロジック文は同一スレッドで動作します つまりログ出力時はログ出力文の実行を待ってから実行する必要があります後続のビジネス ロジック ステートメント。
2.4 非同期ログ
非同期ログを出力する場合、ログ出力文とビジネスロジック文が同一スレッド上で動作するのではなく、ログ出力操作を専用のスレッドで実行するため、ビジネスロジックを処理するメインスレッドは待たされることなく後続の業務を実行できます。論理。
log4j2 の最大の特徴は非同期ロギングであり、そのパフォーマンス向上は主に非同期ロギングによって実現されます。log4j2 の非同期ロギングの使用方法を見てみましょう。
Log4j2 で非同期ログを実装するには、AsyncAppender と AsyncLogger の 2 つの方法があります。
で:
- AsyncAppender は、ArrayBlockingQueue を使用して、非同期に出力する必要があるログ イベントを保存します。
- AsyncLogger は、Disruptor フレームワークを使用して高スループットを実現します。
これらは 2 つの異なる実装方法であり、設計とソース コードでの反映が異なることに注意してください。
2.4.1 非同期アペンダー
AsyncAppender は他の Appender を参照することで実装されており、ログ イベントが到着すると、別のスレッドが開かれて処理されます。アペンダー中に例外が発生した場合、アプリケーションはそれを認識しないことに注意してください。AsyncAppender は、参照する Appender の後に設定する必要があり、他の外部クラス ライブラリを必要とせずに、デフォルトで java.util.concurrent.ArrayBlockingQueue を使用して実装されます。このアペンダーを使用する場合、マルチスレッド環境ではブロッキング キューがロック競合の影響を受けやすく、パフォーマンスに影響を与える可能性があることに注意する必要があります。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<!--正常的Appender配置,此处配置的RollingFile会在下面AsyncAppender被通过name引用-->
<RollingFile name="RollingFileError" fileName="${Log_Home}/error.${date:yyyy-MM-dd}.log" immediateFlush="true"
filePattern="${Log_Home}/$${date:yyyy-MM}/error-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %logger{36} : %msg%xEx%n"/>
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<Policies>
<TimeBasedTriggeringPolicy modulate="true" interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
</RollingFile>
<!--一个Appender配置完毕-->
<!--异步AsyncAppender进行配置直接引用上面的RollingFile的name-->
<Async name="Async">
<AppenderRef ref="RollingFileError"/>
</Async>
<!--异步AsyncAppender配置完毕,需要几个配置几个-->
</Appenders>
<Loggers>
<Root level="error">
<!--此处如果引用异步AsyncAppender的name就是异步输出日志-->
<!--此处如果引用Appenders标签中RollingFile的name就是同步输出日志-->
<AppenderRef ref="Async"/>
</Root>
</Loggers>
</Configuration>
2.4.2 非同期ロガー
AsyncLogger は log4j2 のハイライトであり、公式に推奨される非同期メソッドでもあります。これにより、Logger.log の呼び出しが高速に返されるようになります。Log4j2 の AsyncLogger は内部で Disruptor フレームワークを使用します。
Disruptor の紹介
Disruptor は英国の外国為替取引会社 LMAX が開発した高性能キューで、Disruptor をベースに開発されたシステムは単一スレッドで 1 秒あたり 600 万の注文をサポートできます。
現在、Apache Strom や Log4j2 などの多くの有名なプロジェクトが Disruptor を適用して高いパフォーマンスを実現しています。
Disruptor フレームワークの内部コア データ構造は、ロックフリーのリング キューである RingBuffer です。
なぜディスラプターはそんなに速いのでしょうか?
- ロックフリー - CAS を使用してスレッドの安全性を実現します
- キャッシュ ライン パディングを使用して偽の共有の問題を解決する
非同期ログには、グローバル非同期とハイブリッド非同期の 2 つのオプションがあります。
1) グローバル非同期
グローバル非同期とは、すべてのログが非同期で記録されることを意味します。構成ファイルを変更する必要はありません。jvm の起動時にパラメータを追加するだけで済みます。これは最も単純な構成であり、最高のパフォーマンスが得られます。
次に、log4j2.component.properties 構成ファイルを src/java/resources ディレクトリに追加します。
非同期ログ システム プロパティを設定する
log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
2) 混合非同期
混合非同期とは、アプリケーションで同期ログと非同期ログを同時に使用できることを意味し、ログ構成がより柔軟になります。Log4j ドキュメントには、Log4j2 はほとんどの状態をカバーできる例外処理メカニズムのセットが提供されているものの、完全には処理できない特殊な状況がまだ少数存在するとも記載されているためです。たとえば、監査ログを記録する場合、公式のそれはプログラム ログのみを記録する他の場所では、非同期ログを使用するとパフォーマンスが大幅に向上し、アプリケーション自体への影響が軽減されます。混合非同期メソッドは、構成ファイルを変更し、AsyncLogger を使用して構成をマークすることによって実装する必要があります。
ステップ 1: 関連する jar パッケージを pom に追加する
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
ステップ 2: log4j2.xml 同期ログと非同期ログの混合構成 (AsyncLogger の構成) の例は次のとおりです。
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--status="WARN" :用于设置log4j2自身内部日志的信息输出级别,默认是OFF-->
<!--monitorInterval="30" :间隔秒数,自动检测配置文件的变更和重新配置本身-->
<configuration status="WARN" monitorInterval="30">
<Properties>
<!--1、自定义一些常量,之后使用${变量名}引用-->
<Property name="logFilePath">log</Property>
<Property name="logFileName">test.log</Property>
</Properties>
<!--2、appenders:定义输出内容,输出格式,输出方式,日志保存策略等,常用其下三种标签[console,File,RollingFile]-->
<!--Appenders中配置日志输出的目的地
console只的是控制台 system.out.println
rollingFile 只的是文件大小达到指定尺寸的时候产生一个新的文件-->
<appenders>
<!--console :控制台输出的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--PatternLayout :输出日志的格式,LOG4J2定义了输出代码,详见第二部分 %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL-->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
</console>
<!--File :同步输出日志到本地文件-->
<!--append="false" :根据其下日志策略,每次清空文件重新输入日志,可用于测试-->
<File name="log" fileName="${logFilePath}/${logFileName}" append="false">
<!-- 格式化输出:
%d表示日期,%thread表示线程名,
%-5level:级别从左显示5个字符宽度
%thred: 输出产生该日志事件的线程名
%class:是输出的类
%L: 输出代码中的行号
%M:方法名
%msg:日志消息,
%n是换行符
%c: 输出日志信息所属的类目,通常就是所在类的全名
%t: 输出产生该日志事件的线程名
%l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.Java:10)
%p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
2020.02.06 at 11:19:54 CST INFOcom.example.redistest.controller.PersonController 40 setPerson - 添加成功1条数据
-->
<!-- %class{36} 表示 class 名字最长36个字符 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!--
关键点在于 filePattern后的日期格式,以及TimeBasedTriggeringPolicy的interval,日期格式精确到哪一位,interval也精确到哪一个单位.
1) TimeBasedTriggeringPolicy需要和filePattern配套使用,由于filePattern配置的时间最小粒度如果设置是dd天,所以表示每一天新建一个文件保存日志。
2) SizeBasedTriggeringPolicy表示当文件大小大于指定size时,生成新的文件保存日志。与%i配合使用-->
<RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<!--ThresholdFilter :日志输出过滤-->
<!--level="info" :日志级别,onMatch="ACCEPT" :级别在info之上则接受,onMismatch="DENY" :级别在info之下则拒绝-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<!-- Policies :日志滚动策略-->
<Policies>
<!-- TimeBasedTriggeringPolicy :时间滚动策略,默认0点小时产生新的文件,interval="6" : 自定义文件滚动时间间隔,每隔6小时产生新文件, modulate="true" : 产生文件是否以0点偏移时间,即6点,12点,18点,0点-->
<TimeBasedTriggeringPolicyinterval="6" modulate="true"/>
<!-- SizeBasedTriggeringPolicy :文件大小滚动策略-->
<SizeBasedTriggeringPolicysize="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</appenders>
<!--3、然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<!--Logger节点用来单独指定日志的形式,name为包路径,比如要为org.springframework包下所有日志指定为INFO级别等。 -->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<!-- Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出 -->
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
<!--AsyncLogger :异步日志,LOG4J有三种日志模式,全异步日志,混合模式,同步日志,性能从高到底,线程越多效率越高,也可以避免日志卡死线程情况发生-->
<!--additivity="false" : additivity设置事件是否在root logger输出,为了避免重复输出,可以在Logger 标签下设置additivity为”false”只在自定义的Appender中进行输出
-->
<AsyncLogger name="AsyncLogger" level="trace" includeLocation="true" additivity="false">
<appender-ref ref="RollingFileError"/>
</AsyncLogger>
</loggers>
</configuration>
2.5 非同期ログを使用する場合の注意事項
非同期ログを使用する場合は、次の点に注意する必要があります。
- AsyncAppender と AsyncLogger を同時に使用しないでください。つまり、Appender を構成するときに、Async ロゴを使用せず、同時に AsyncLogger を構成しないでください。これによってエラーは発生しませんが、パフォーマンスの向上には何のメリットもありません。
- グローバル同期がオンになっている場合は、引き続き AsyncAppender と AsyncLogger を使用しないでください。これは前のものと同じ意味を持ちます。つまり、非同期ログを使用する場合、AsyncAppender、AsyncLogger、およびグローバル ログが同時に表示されるべきではありません。
- 絶対に必要でない場合は、同期か非同期かに関係なく、immediateFlush を false に設定すると、パフォーマンスが大幅に向上します。
- 本当に必要でない場合は、HTML の場所、%C または $class、%F または %file、%l または %location、%L または %line、%M または %method などの位置情報を出力しないでください。パターン モード: Log4j はログを出力するときにスタックのスナップショットを取得してこの情報を取得する必要があるため、これによりパフォーマンスが大幅に低下します。
3 まとめ
ストレステストでは、問題の根本原因の探索と発掘に全力を尽くし、常に実践経験を蓄積してまとめます。特に一部のオープン ソース コンポーネントを使用する場合は、使用仕様とベスト プラクティスを詳細に学習して理解する必要があり、必要に応じてパフォーマンス テストを追加して、品質とパフォーマンスの要件を満たしていることを確認できます。
4 参考
- https://www.yisu.com/zixun/623058.html
- https://www.jianshu.com/p/9f0c67facbe2
- https://blog.csdn.net/thinkwon/article/details/101625124
- https://zhuanlan.zhihu.com/p/386990511
著者: JD Logistics Liu Jiangbo Lu Yihao
出典:JD Cloud Developer Community Ziyuanqishuo Tech 転載の際は出典を明記してください
Qt 6.6が正式リリース Gomeアプリの抽選ページのポップアップウィンドウが創設者を侮辱 Ubuntu 23.10が正式リリース 金曜日を利用してアップグレードするのもいいかもしれません! RISC-V: 単一の企業や国によって管理されていない Ubuntu 23.10 リリース エピソード: ヘイトスピーチが含まれているため ISO イメージが緊急に「リコール」された ロシアの企業は Loongson プロセッサをベースにしたコンピュータとサーバーを製造している ChromeOS は Google デスクトップを使用する Linux ディストリビューション環境 23 歳の 博士課程学生が Firefox の 22 年前の「ゴーストバグ」を修正 TiDB 7.4 リリース: MySQL 8.0 と正式互換 Microsoft が Windows Terminal Canary バージョンを発表