1. コア金融取引システムで不定期にクラッシュが発生する。
障害現象:本システムは金融業務の基幹取引(トレード)システムであり、その中核となるのは決済注文の業務です。サービスは 4 台のマシンで行われ、トラフィックはそれほど大きくありません。このインシデントは、コードの一部を API システムから取引に移行した後に発生しました。その後、展開後、4 台のマシンのうち 1 台が必ず 3 ~ 4 日以内に Java プロセスをクラッシュさせます。
サービス展開ステータス:
4 つの ECS サーバーの場合、サービス起動スクリプトはおおよそ次のとおりです。
java -Xmx4g -Xms4g -Xlog:gc:gc.log -XX:+HeapDumpOnOutOfMemoryError -jar trade.jar
gc.log が出力され、サービスヒープがオーバーフローした場合にもログが出力されます。
補足:
1. サーバーは 4 つありますが、毎回クラッシュするのは 1 つだけです。
2. このサービスは consul によって管理されているため、クラッシュ後は自動的に削除されるため、ビジネスへの影響は非常に小さいです。
3. クラッシュの現象は、マシン上の Java プロセスが直接失われることです。ps -ef|grep 'java' を使用します。もうプロセスはありません。
トラブルシューティングの手順:
1. consul アラーム受信後、すぐにサービスログと gc ログを確認し、ヒープメモリオーバーフローによるログが発生していないか確認しましたが、結論としては異常はありません。
2. サービスのシステム ログをチェックして、システムによってサービスが強制終了されていないかどうかを確認します。
- /var/log/syslog
- /var/log/messages
結論としては、Java プロセスがシステムによって強制終了されたことを示すものはなく、関連するログもシステム ログに見つかりませんでした。
3. 現時点で、問題の調査は行き詰まっています。そこで、次の 2 つの決定が下されました。
a. サービスの重大なクラッシュの本当の理由について、より多くのログを収集します。
b. クラッシュ後にサービスが自動的に復元できることを確認します。
解決策の手順:
質問aの場合:
次のパラメータを起動スクリプトに追加します。
XX:ErrorFile=./hs_error%p.log (当jvm发生致命错误崩溃时会生成一个这样的文件)
質問 b の場合:
次に、開始された Java プロセスを監視する必要があり、クラッシュ後に自動的にプルアップできます。systemd を使用してプロセスを管理することにしました。
systemd はプロセスを監視し、プロセスがクラッシュすると、自動的にプロセスを停止します。
上記の gpt の概要は基本的に正確です。
問題の原因:
ログを追加した後、ある午後、サーバー上でこのログが確認されました。/tmp/hs_err_pid16905.log
一般的な内容は次のとおりです。
A fatal error has been detected by the Java Runtime Environment:
この段落では、JVM の致命的なエラーにより JVM が終了した (Tomcat がクラッシュした) ことを示します。
Current thread (0x00007f3474182ae0): JavaThread "C2 CompilerThread0" daemon [_thread_in_native, id=16915, stack(0x00007f34619c3000,0x00007f3461ac4000)]
致命的エラーが発生した現在のスレッドは次のとおりです。「C2 CompilerThread0」という名前のスレッドは、C2 コンパイラのコンパイル スレッドであり、ネイティブ コードまたは JNI コードを実行しています。スレッドのスタック領域アドレスは 0x00007f34619c3000、0x00007f3461ac4000 ~ 0x00007f34619c3000、0x00007f3461ac4000 です。
siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x000000000000002c
これは、jvm がポインタを介してメモリ アドレス 0x000000000000002c にアクセスしようとし、その結果、不正なメモリ アクセス エラー SIGSEGV が発生することを意味します。
Current CompileTask:
C2:54539027 25824 4 com.sogou.loan.api.service.impl.TradeApiServiceImpl::progressList (1016 bytes)
これは、現在の jvm がタスクをコンパイルしていることを意味します。そこから、jvm の c2 コンパイラーがクラス com.sogou.loan.api.service.impl.TradeApiServiceImpl の progressList メソッドをコンパイルしていることがわかります。
結論は:
https://bugs.java.com/bugdatabase/view_bug?bug_id=8187629
Java™ SE Development Kit 8、Update 172 リリースノート
この問題は jvm のコンパイラのバグです
今回のトレードサービスクラッシュ判定は、jvmのc2コンパイラのコンパイラバグが原因でした。c2 コンパイラの条件付き移動の最適化により、破損した参照グラフが作成され、プログラム ポインタがシステムで保護されたアドレスを指すようになり、メモリ違反により jvm が致命的なエラー クラッシュを生成する可能性があります。このバグはバージョン 8u 172 で修正されました。公式リリースノートによると
修正:
1. 一時的な修正: サービスが開始されると、問題のあるコードは C2 コンパイルから禁止されます。
XX:CompileCommand=exclude,com/sogou/loan/api/service/impl/TradeApiServiceImpl,progressList
2.徹底した補修
.jdk が jdk8 の 8u171 バージョンから 8u211 バージョンにアップグレードされました
知識の補足:
JIT コンパイラの最適化
レイヤ 0: プログラムの解釈と実行
レイヤ 1: C1 コンパイル、バイトコードをネイティブ コードにコンパイルし、シンプルで信頼性の高い最適化を実行
レイヤ 2: C2 コンパイル、バイトコードをローカル コードにコンパイルし、コンパイルします コンパイルに時間のかかる最適化を有効にします。パフォーマンスに応じて監視される場合があります
2. ゲートウェイシステムにおける非同期タスクの実行の遅延
症状:
これはリスク管理システムであり、一般的な業務実行ロジックとしては、機能サービスが必要な機能を取得 → ゲートウェイシステムが必要な機能に応じて非同期に外部サービスを要求 → 非同期コールバックゲートウェイサービスの機能取得が完了 - →通知 特徴サービスの計算が開始されます。
アラームは 1 日で受信されました。ほとんどの遅延は特徴量の計算にありました。
トラブルシューティング:
1. アラームを受信する前日はオンラインだったので、コードはできるだけ早くロールバックされました。(ためらわないでください)。
2. コードをロールバックした後、特徴量の計算速度を確認すると、通常の速度に戻ります。
トラブルシューティング:
1. リスク管理システムに関連するサブシステムを分析したところ、ゲートウェイ システム上で以下のコードが起動されていることが判明しました。
@Bean
public ScheduledExecutorService scheduledThreadPool() {
return Executors.newScheduledThreadPool(5);
}
予備分析はこのスレッド プールに関連している可能性があります (ここでは、スレッド プールが誤って使用されており、スレッド プールは可能な限りプライベート変数として使用する必要があります)
2. 次に、特徴量計算のコード ロジックを整理すると、ゲートウェイ システムの非同期呼び出しが @Async を使用していることがわかります。指定されたスレッドプールは表示されません。
3. このマシンでデバッグに移動すると、@Async が ScheduledExecutorService のスレッド プール (遅延スレッド プール) を使用していることがわかりました。
4. 次に、@Async のソース コードを分析し、指定されたスレッド プールが表示されない場合は、コンテナー内の ExecutorService のサブスレッド プールに移動することを確認します。コンテナーにスレッド プールがない場合は、デフォルトの新しいスレッド プールが使用されます。したがって、@Async アノテーションを使用すると、指定されたスレッド プールを表示する必要があります。
5. 上記スレッドプールの宣言方法を修正しました。オンラインに戻ります。
3. ゲートウェイ システムのヒープ外メモリ オーバーフロー障害
症状:
ゲートウェイ システムのホストは oom からアラームを受信し、consul もサービス ノードがダウンしたというアラームを受信しました。
トラブルシューティング:
1. サーバーにログインすると、サービスの Java プロセスが消えていることがわかります。そのため、サービスを緊急に再起動します。
2. 明らかな例外がないアプリケーション ログを確認します。 3.
jvm の対応するディレクトリに新しい jvm ダンプ ログがありません。ダンプ ログ
4. jvm gc ログは対応するディレクトリにあります。 5.
jvm エラー ログを確認します。新しいエラー ログはありません。
6. syslog /var/log/messages を確認します。4
月 26 日 10:44:17 VM_0_86_centos カーネル: Java が呼び出されました。 oom-killer: gfp_mask=0x201da、order= 0、oom_score_adj=0
**4 月 26 日 10:44:18 VM_0_86_centos カーネル: 強制終了されたプロセス 11154 (java) total-vm:10288940kB、anon-rss:6899844kB、file-rss:0kB、shmem-rss:0kB**
トラブルシューティング:
OS カーネルの oom-killer メカニズムがトリガーされ、Java プロセスが OS によって自動的に強制終了されます。強制終了前の Java プロセス
の total_vm は 10288940kB (約 9.8G)、anon-rss (実際に占有されている物理メモリ)システム メモリは 6899844kB (6.58G) です
8G ではありません。Linux システムの原則によれば、オペレーティング カーネルはメモリ スコアが最も高いプロセスを強制終了します
起動設定を確認してください java -Xmx4g -Xms4g -Xmn1365m. 最大ヒープは 4G です。
次に、他のオフヒープ メモリが見つかるはずです。最大以前のオフヒープ メモリがないことがわかります。
-XX:MaxDirectMemorySize は 1G に設定されています。
知識の補足:
1. Java でオフヒープ メモリを割り当てるには、ByteBuffer#allocateDirect と Unsafe#allocateMemory という 2 つの方法があります。
おそらく最初のメソッドは頻繁に使用されるでしょう。これは Java NIO が提供するメモリ割り当てクラスです。このメソッドはネットワーク開発を行うときにメモリを割り当てるためによく使用されます。2 番目のメソッドは Unsafe メソッドです。Unsafe は安全でないクラスであることがわかっています。 。このクラスは、開発者が最下位レベルのデータを操作するために提供されています。これは、C または C++ がメモリを直接操作する方法と似ています。したがって、このクラスは推奨されません。このクラスを使用してメモリを割り当てたが、再利用に失敗した場合は、時間が経つと、メモリリークが発生しやすくなります。
最初の方法: ByteBuffer#allocateDirect
//10M メモリを割り当て ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);
この方法でオフヒープ メモリを割り当てるために、最下層は引き続き Unsafe#allocateMemory を使用してメモリを割り当て、ByteBuffer は Unsafe をカプセル化するだけです。
2. オフヒープメモリを再利用する方法
JVM がオフヒープ メモリを再利用する
ByteBuffer#allocateDirect を通じてオフヒープ メモリが割り当てられると、オフヒープ メモリのアドレス、サイズ、その他の情報が DirectByteBuffer を通じて関連付けられ、ヒープ メモリをオフヒープ メモリに関連付けることができます。
まず、DirectByteBufferはヒープメモリ上に存在するオブジェクトなので、ヒープメモリ上に存在するためGCプロモーション、つまり古い世代に昇格することになり、フルGCやオールドGCが発生します。古い世代
注意点1:
実際に DirectByteBuffer を使用する場合、メモリを使い果たさないようにしますが、実際の運用では追加ヒープ メモリがどのくらい残っているかわからない場合があるため、JVM のパラメータで制御し、追加ヒープ メモリのサイズを指定することができます。 JVM パラメーターを介して -XX:MaxDirectMemorySize 上限サイズ。指定されたメモリ上限サイズを超えると、メモリを再利用するためにフル GC がアクティブにトリガーされます。
注意点2:
DirectByteBuffer を介してメモリを割り当てる場合、割り当てられたメモリが不十分である可能性があるため、JVM はヒープ外メモリの割り当てが不十分であることを検出すると、積極的に GC を開始しますが、今回の GC は、実装された強制 GC です。 System.gc() ただし、実際の運用環境では、JVM パラメータ -XX:+DisableExplicitGC を使用して System.gc() の使用を禁止しているため、メモリ不足を避けるために実際の使用時にメモリの割り当てに注意する必要があります。漏れます。
4. スレッド プール ツールの誤った使用によりメモリが急増する
症状:
ある朝、サービスがメモリ使用量が 90 を超えたと報告するというアラームが発生しました。
トラブルシューティング:
1. メモリ曲線を観察すると、前の晩にオンラインになってからメモリが増加していることがわかります。
2.オンラインコードを断固としてロールバックします
故障解析:
1. まず、前日にオンラインになったコードを分析します。一般的な疑似コードは次のとおりです。
//声明一个线程池
ExecutorService exs = Executors.newFixedThreadPool(10);
//声明 CompletionService
CompletionService<Integer> completionService = new ExecutorCompletionService<>(exs);
//2.添加任务(向CompletionService添加任务 然后把返回的futrue添加到futureList即可)
int taskCount = 10;
for (int i = 0; i < taskCount; i++) {
futureList.add(completionService.submit(new Task(i + 1)));
}
//获取结果
for (Future<Integer> future : futureList) {
System.out.println("====================");
Integer result = future.get();//线程在这里阻塞等待该任务执行完毕,按照
System.out.println("任务result="+result+"获取到结果!"+new Date());
list.add(result);
}
2. 問題分析
ExecutorCompletionService のソースコードを分析することでわかります。ExecutorCompletionServiceは、タスクを投入したFutureTaskの結果をBlockingQueueに格納し、実行完了時に最初にキューに入ります。
結果を取得したら、LinkedBlockingQueue から take() するだけです。
ただし、結果を取得しない場合でも、FutureTask の get() を使用することになります。その後、LinkedBlockingQueue (無制限のキュー) が長くなり、メモリがどんどん上昇していきます。
したがって、上記の失敗は ExecutorCompletionService の誤った使用によって引き起こされます。