高負荷のJavaアプリケーション

この記事に関して、Javaの負荷の高いシステムについて追加したいと思います。 コメントを書くのが多すぎるので、個別に書きます。 このアプローチはプログラマーではありませんが、誰かに役立つかもしれません。 これは私の意見です。おそらくどこか間違っているでしょう。



JEEプラットフォーム



もちろん、標準のJavaでは、アプリケーションの実行速度が速くなり、メモリの消費が少なくなります。また、アーキテクチャを必要なとおりに作成できます。 ただし、これには工数が無制限であり、毎日10万人のクライアント用のアプリケーションのようなナンセンスなものを書くことが前提になっています。 そうしないと、これには多くの時間と反復が必要になり、最終的には最新のJEEサーバーでの実装方法よりもやや悪化します。 すべてのミスは実際の負荷の下でのみ発生し、アプリケーションアーキテクチャを複数回やり直す必要があります。



Web用のJava.nio



非ブロッキング出力はjava.nioに登場しました。つまり、新しい接続ごとにスレッドを開始する必要はありません。 1つのスレッドで、頻繁なコンテキストの切り替えやメモリのロードを行わずに、数百以上の接続を処理できます。 しかし、これは万能薬ではありません。このアプローチには重大な欠点があります。フローの遅延はキュー内のリクエストに影響します。 場合によっては、「1つの接続-1つのスレッド」アプローチが望ましい場合があります。



Linux側では、epollには2.6+カーネルが必要です。 Solarisの場合、イベントポートをサポートするには、java 8と-Djava.nio.channels.spi.Selector = sun.nio.ch.EventPortSelectorProviderパラメーターが必要です。そうでない場合は/ dev / pollがありますが、これも悪くありません。



スレッドプール



異なる操作に対して異なるプールが作成されるほど、システムのスケーリングと負荷の管理が容易になりますが、スレッドの処理、スレッド間の要求の転送、および同期のためのオーバーヘッドが増えます。



少なくとも、リスナによって受け入れられた接続を取得し、それらを使用してすべてのアクションを実行するプールが1つ必要です。そのサイズは、システムが処理できる最大値に設定する必要があります。 満杯の場合、着信接続は受け入れられません。 ただし、DOSが負荷や攻撃を増やすのを防ぐために、必ず制限する必要があります。 ブロック操作を使用しないでください。すべてのブロック操作は別々のスレッドで実行する必要があります。



たとえば、平均実行時間が5ミリ秒の100スレッドのプールは、1秒あたり2万のリクエストに対応できます。 そして、50msの外部サービスからの応答を待たなければならない場合、処理されるリクエストの数は1.8kに削減されます。 すべてのリクエストにサービスコールが必要でない場合は、非同期処理を行うことをお勧めします。



プールはすぐに最大値を作成する必要はありません。負荷が増加するにつれてプールを増やし、負荷が減少するにつれてプールを減らす方が適切です。 スレッドは、アイドル状態のスレッドであっても、メモリを消費し、システム全体の速度を低下させます。 Javaのスレッド== OSのスレッド、CPUスレッドのプールサイズが大きすぎると、パフォーマンスが低下します。



データベースへの接続のプール



プールを使用する目的は、接続の確立と接続数の制御にかかる時間を節約することです。 プールを自分で作成し、JEEの場合に使用可能なプールを使用しない場合は、タイミングを慎重に設定する必要があります。 接続が長すぎると予想される場合、これは実行のスレッドの遅延と対応するプールのサイズの増加につながります。 障害が発生した場合にプールを再作成するときは、接続を再試行するタイミングを実行する必要があります。それをプログレッシブにする価値がある場合があります。そうしないと、誤ってサーバーにDDoS攻撃を行う可能性があります。



これは、データベースだけでなく、httpなどの他の外部リソースにも適用されます。



キュー



これらはスレッド間の相互作用に必要です。実装では、これは何らかの種類のLinkedListまたはLinkedBlockingQueueです。 これらは大量のメモリを消費し、tcpまたはhttpタイムアウト中にスレッドがキューからのリクエストを処理する時間がない場合があるため、大きすぎてはいけません。 同時に、それらは負荷の短期バーストをスムーズにするのに十分な大きさでなければなりません。 推定値は、処理プールのサイズの10倍です。



JSP



キャッシュを使用しても、サーブレットよりも低速になるため、負荷の高いシステムにはJSPを使用しないでください。 問題なくシステムを水平方向にスケーリングできるようにするには、サーバーに状態を保存せずにサービスを実行する必要があります。



ロギング



最低限必要な情報を記録する必要があることは明らかです。 重要な点:特別な要件がない場合、ロギングはシステム障害につながるべきではありません。 つまり、ログを書き込むことができなかった場合、System.errにログについて書き込み、さらに作業を行い、実行でストリームを終了しません。 ログは時間内にサイズを変更できますが、無限であってはなりません。 幸いなことに、ロギング用のすべてのJavaライブラリはこれで素晴らしい仕事をします。



余分なサイクルを無駄にしないために、デバッグログの準備と出力を類似したものにラップすることをお勧めします。



if(log.isDebugEnabled()){ StringBuilder sb = new StringBuilder(); sb.append(arg0); sb.append(arg1); log.debug(sb.toString()); }
      
      





Java監視



負荷を評価するには、リアルタイムで、ヒープ、スレッドプール、データベースへの接続プール、その他のリソース、セッションのサイズを確認することが重要です。 評価用の履歴データも必要です。 jcontrolまたは同様の機能を介して監視できますが、あまり便利ではありません。 これらのメトリックを収集して、個別のソフトウェアとして、または独立してデータベースに書き込むことをお勧めします。 状態を評価するためのメトリックの数は最小限である必要があります。これは、システムリソースも必要とするためです。多数の接続がある場合、これは顕著になります。



デッドロックスレッド



通常のJEEサーバーでは、スレッドとスレッドプールを使用しないでください。 サーバーツールはそれほど便利ではない場合でも使用することをお勧めします。 実際、サーバーはフローを監視し、デッドロックをキャッチしますが、サーバーについては何も知りません。



独自に作成する必要がある場合は、デッドロックを追跡し、ロックして強制的に完了する必要があります。 多数のスレッドの場合、最も予期しない場所でデッドロックが発生します。



All Articles