|
Javaの世界では、Nettyは間違いなくネットワークアプリケーション開発の頼りになるツールです。複雑なNIOモデルや基盤となるネットワークの詳細についてあまり心配する必要はありません。豊富なインターフェースを使えば、複雑な通信機能も簡単に実装できます。
Goのネットワークモジュールと比べると、Nettyはまだ肥大化しています。しかし、Javaフレームワークはそういうものなのです。IDEなしでは機能しないようなプログラミング言語なのです。 *** の Netty バージョンでは、モジュールが非常に細かく分割されています。各モジュールに何が含まれているか不明な場合は、netty-all を使用してください。 純粋に使い方の観点から言えば、Nettyは非常にシンプルです。ByteBuf、Channel、Pipeline、そしてEventモデルをマスターすれば開発には十分です。Netty関連の面接では、Nettyについて話すことはほとんどありません。しかし、Nettyは他の開発モデルとは大きく異なり、主に非同期性という点が異なります。この非同期性により、異なるプログラミングモデルが生まれ、デバッグの難しさやコーディング基準の厳しさも増します。なぜなら、Nettyにおけるバグのコストは、ビジネスロジックにおけるバグのコストとは比較にならないからです。 しかし、プロジェクトの観点から見ると、規模は小さいものの、必須コンポーネントはすべて揃っています。ビジネスレイヤーからサービスゲートウェイ、そして監視や設定といった様々な技術的安全対策に至るまで、あらゆる要素を考慮する必要があります。Netty自体が占める割合はごくわずかです。 この記事では、Nettyを用いた開発において注目すべき共通点について解説し、1台のマシンで100万接続をサポートするためのLinux構成を紹介します。この記事ではNettyの基礎については扱いません。 プロトコル開発 Web開発において最も重要なのは、通信フォーマットとプロトコルです。一般的な例としては、protobuf、json、avro、MQTTなどが挙げられます。プロトコルには、構文、セマンティクス、タイミングという3つの重要な要素があります。 私は、Redis プロトコルを使用しながらバックエンドに MySQL を実装しているミドルウェア アプリケーションを数多く見てきました。また、プロキシ側シャーディング ミドルウェア、TiDB など、MySQL プロトコルを使用するカスタム ストレージ システムも数多く見てきました。 私たちがよく使うRedisはテキストベースのプロトコルを使用していますが、MySQLなどはバイナリプロトコルを実装しています。Nettyも同様で、コーデック(DecoderまたはEncoderファミリーを継承したもの)を実装するだけで済みます。NettyはDNS、HAProxy、HTTP、HTTP/2、Memcached、MQTT、Redis、SMTP、SOCKS、STOMP、XMLなどのプロトコルをデフォルトで実装しており、非常に包括的で、直接使用するのに非常に便利です。 考えられる製品構造としては、外観は一貫しているものの、コアストレージが異なる次のようなものがあります。 テキストプロトコルは比較的直感的でデバッグも容易ですが、セキュリティは低いです。一方、バイナリプロトコルはログやWiresharkなどの解析が必要となり、開発の複雑さが増します。悪名高いパケットの断片化と再構成の問題もここで取り上げます。パケットの断片化の主な原因はバッファの介在であるため、送信側と受信側の間で送信サマリーなどの情報に合意する必要があります。Nettyはこの問題をある程度解決します。 Webアプリケーション開発を目指す学生は皆、プロトコルを再設計したいという夢を抱いています。しかし、プロトコル設計は非常に難しく、関連するビジネスロジックの深い理解とスケーラビリティの考慮が求められます。どうしても必要な場合を除き、既存のプロトコルを使用することをお勧めします。 接続管理機能 Nettyの開発において、接続管理は非常に重要です。通信品質、システムステータス、そして一部の高度な機能はすべて接続管理に依存しています。 Nettyはサーバーとして動作する場合もクライアントとして動作する場合も、接続を確立した後にChannelと呼ばれるオブジェクトを取得します。私たちの役割はそれを管理することです。私は通常、これをConnectionManagerと呼んでいます。 管理クラスは、メモリオブジェクトをキャッシュして実行時データを収集します。例えば、コネクション指向の機能には、パケットの送受信回数、パケットの送受信速度、エラー数、接続再接続回数、呼び出し遅延、接続状態などが含まれます。これらの機能は、バグが集中しやすいJavaの`concurrent`パッケージのクラスを頻繁に利用します。 しかし、それだけではありません。管理クラスは、各接続にさらに多くの機能を提供します。例えば、接続が確立された後、いくつかの機能をウォームアップしたい場合、これらの状態をルーティング決定に利用することができます。通常、接続にユーザーやその他のメタデータを添付することで、条件に基づいて接続を多次元的にフィルタリングできるようになり、カナリアデプロイや過負荷保護などのバッチ処理が可能になります。これは非常に重要な機能です。 管理バックエンドでは、各接続の情報を確認できます。1つまたは複数の接続をフィルタリングした後、これらの接続に対してトラフィック記録、情報監視、ブレークポイントデバッグを有効にすることで、完全な制御が可能になります。 管理機能により、ユーザーはシステム全体の動作状態を確認し、負荷分散戦略をタイムリーに調整できるほか、スケールアップやスケールダウンのためのデータも提供されます。 心拍数検出 アプリケーション プロトコル層でのハートビートは必須です。これは TCP キープアライブとはまったく異なる概念です。 アプリケーション層プロトコル層のハートビートは、接続における双方の生存状態と接続品質を検出しますが、キープアライブは接続自体の生存状態を検出します。さらに、後者のデフォルトのタイムアウトは長すぎて、現代のネットワーク環境には全く適していません。 ハートビートは、サーバー側でもクライアント側(GCMなど)でもポーリングに依存します。キープアライブメカニズムは、アプリケーションのシナリオに応じて動的に切り替わります。例えば、ポーリング戦略は、アプリケーションがバックグラウンドで実行されているかどうかによって異なります。 Nettyには、IdleStateHandlerを介してIDLEイベントを生成することで、便利なハートビート制御を提供する組み込みシステムがあります。遅延再接続など、ハートビートのタイムアウトロジックを処理する必要があります。ただし、ポーリング時間は固定されており、動的に変更することはできません。高度な機能を使用するには、カスタムカスタマイズが必要です。 Android などの一部のクライアントでは、頻繁なハートビートによってネットワークとバッテリー電力が大量に浪費される可能性があるため、ハートビート戦略はより複雑になります。 境界 優雅な退出メカニズム Java での正常なシャットダウンは、通常、JDK ShutdownHook を登録することによって実現されます。 Runtime.getRuntime().addShutdownHook(); Java プロセスは通常、プロセスが終了する前にクリーンアップ作業を実行するために `kill -15` を使用して終了します。 注意: kill -9 はプロセスに終了の通知をする機会を与えずに直ちにプロセスを強制終了します。これは非常に危険です。 Nettyは、`EventLoopGroup`の`shutdownGracefully`メソッドを通じてNIOの状態設定を行うなど、正常なシャットダウンに多くの処理を施していますが、それだけでは不十分な場合が多くあります。Nettyは、単一マシン環境での正常なシャットダウンのみを処理します。 トラフィックが外部ルートを経由して流入し続け、無効なリクエストが発生する場合があります。私は通常、まず外部ルートのローカルインスタンスを削除してトラフィックを遮断し、その後Netty自体を正常にシャットダウンします。この設計は非常にシンプルで、関連するインターフェースがルーティング層で事前に公開されていれば、リトライメカニズムがなくても問題なく動作します。 例外処理関数 Nettyは非同期開発アプローチとイベントメカニズムを採用しているため、例外処理を非常に重視しています。高い接続信頼性を確保するため、多くの例外はユーザー空間で暗黙的に無視されるか、検出されないままにしておく必要があります。 Netty例外はパイプラインを介して伝播するため、どのレイヤーでも処理可能です。ただし、プログラミング規約では、例外は集中処理のために最外層にスローされることが一般的です。 例外情報を可能な限り区別するために、通常は多数の例外クラスが定義されており、異なるエラーには異なる例外がスローされます。例外発生後、その種類に応じて、切断と再接続(例:一部のバイナリプロトコルにおけるエンコード/デコードエラー)、または別のノードへの再ルーティングなどのオプションを選択できます。 機能上の制限 コマンドモード ネットワークアプリケーションはネットワークアプリケーションとしてのみ使用すべきです。あらゆる通信はコストがかかります。「Linux: Cast Away (Part 5) - ネットワーク」では、数百万の接続を持つサーバーが1KBのメッセージをブロードキャストするには1000MBの帯域幅が必要であることを説明しました。したがって、すべてをネットワークアプリケーションに組み込むことはできません。 大規模なWebアプリケーションの場合、適切なコマンドを送信するのが論理的なアプローチです。クライアントはコマンドを受信すると、HTTPなどの他の方法を用いて大容量ファイルを取得します。多くのインスタントメッセージング(IM)アプリケーションは、この原則に基づいて設計されています。 コマンドラインモードは、通信システムのスケーラビリティと安定性も確保します。コマンドの追加は設定可能で、即座に有効になり、サーバー側のコーディングや再起動は必要ありません。 安定性保証 Webアプリケーションは通常、非常に高いトラフィックを生成するため、完全なログ記録は適していません。アプリケーションは重要なイベントのログ記録と例外処理に重点を置き、ログ記録は控えめに行うべきです。 ネットワークアプリケーションは、他の低速APIやI/Oブロッキングインターフェースの呼び出しにも適していません。リアルタイムイベントはAPI呼び出しではなく、高速メッセージキューなどの非同期チャネルを使用する必要があります。 キャッシュは、Webアプリケーションで最も頻繁に使用されるコンポーネントと言えるでしょう。JVMベースのキャッシュは、特定のマシンの統計情報を保存できますが、Redisなどのサービスは、グローバル統計情報と中間データを保存できます。 ネットワークアプリケーションは、ユーザーリクエストに迅速に応答するために、Redis、キーバリューペア、高スループットのメッセージキューを多用します。つまり、通信層を可能な限りクリーンに保つことで、多くのトラブルを回避できるのです。 1台のマシンで100万の接続をサポートするLinux構成 1台のマシンで100万件の接続をサポートすることは可能ですが、帯域幅が大きなボトルネックとなります。圧縮バイナリプロトコルを有効にすると帯域幅をいくらか節約できますが、開発の複雑さは増します。 「LWPプロセスのリソース枯渇、リソースが一時的に利用不可」で説明したElasticsearchの設定と同様に、最適化も同様のアプローチで行われます。この設定により、数日間の時間を節約できますので、ぜひご活用ください。 オペレーティングシステムの最適化 プロセス***ファイルハンドルの数を変更する
単一のプロセスで割り当てることができるファイルの数を変更します。
/etc/security/limits.conf ファイルを変更する
/etc/security/limits.d/* の下の設定を必ずクリーンアップしてください。 ネットワーク最適化 /etc/sysctl.conf を開いて設定を追加します。 次にコマンドを実行し、sysctl を使用して有効にします。
要約 Nettyの開発作業は、Netty自体ではなく、サービスの高い信頼性と安定性の確保に重点を置いています。バグ修正コストを削減するために、監視とデバッグにもかなりの作業が費やされています。 Nettyを深く理解することで、複雑なシステムの問題のトラブルシューティングや、要求の厳しいパフォーマンスの向上に役立ちます。しかし、ほとんどのアプリケーション開発者にとって、Nettyの学習曲線は緩やかであり、その基盤となるメカニズムを深く理解しても大きなメリットは得られません。 それは単なるツールです。これ以上何ができるのでしょうか? |