1 チャネルインターフェースのライフサイクル
Channel は、ChannelInboundHandler API に密接に関連する、シンプルだが強力な状態モデルを定義します。
1.1 チャンネルステータス
州 | 説明 |
---|---|
チャンネル未登録 | チャンネルは作成されましたが、まだ EventLoop に登録されていません |
チャンネル登録済み | EventLoopにチャンネルが登録されました |
チャンネルアクティブ | チャネルはアクティブです (リモート ノードに接続されています)。データの送受信ができるようになりました |
チャンネル非アクティブ | チャネルがリモート ノードに接続されていません |
1.1.1 チャネル状態モデル
チャネルの通常のライフサイクルを以下の図に示します。これらの状態が変化すると、対応するイベントが生成されます。
これらのイベントは ChannelPipeline の ChannelHandler に転送され、ChannelHandler はそれに応答できます。
2 ChannelHandler のライフサイクル
ChannelHandler が ChannelPipeline に追加または削除されたときに呼び出される ChannelHandler インターフェイスのライフサイクル操作。これらの各メソッドは、ChannelHandlerContext パラメーターを受け取ります。
タイプ | 説明 |
---|---|
ハンドラー追加 | ChannelHandler が ChannelPipeline に追加されるときに呼び出されます |
ハンドラーが削除されました | ChannelHandler が ChannelPipeline から削除されるときに呼び出されます。 |
例外をキャッチしました | 処理中に ChannelPipeline でエラーが発生したときに呼び出されます |
Netty は次の ChannelHandler サブインターフェイスを定義します。
- ChannelInboundHandler、受信データとさまざまな状態変更を処理します
- ChannelOutboundHandler、アウトバウンドデータを処理し、すべての操作のインターセプトを許可します
3 ChannelInboundHandler
ChannelInboundHandler インターフェイスのライフサイクル。
3.1 電話を受けるタイミング
- データ受信時
- または、対応するチャネル状態が変化したとき
これらのメソッドはチャネルのライフサイクルと強く関連しています。
① Channel から読み取れるバイトが全て読み込まれるとコールバックメソッドが呼び出されるため、channelRead 内にある可能性があります。
Complete() が呼び出される前に、channelRead(...) が複数回呼び出されます。
ChannelInboundHandler 実装が channelRead() メソッドをオーバーライドすると、プールされた ByteBuf インスタンスに関連付けられたメモリを明示的に解放する必要があります。Netty はこのために ReferenceCountUtil.release() を提供します
代码清单 6-1
@Sharable
// 扩展了 ChannelInboundHandlerAdapter
public class DiscardHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 丢弃已接收的消息
ReferenceCountUtil.release(msg);
}
}
Netty は、未解放のリソースを WARN レベルのログ メッセージで記録するため、コード内の違反のインスタンスを非常に簡単に見つけることができます。ただし、この方法でリソースを管理するのは面倒な場合があります。SimpleChannelInboundHandler を使用する方が簡単です。リスト 6-2 は、この点を示すリスト 6-1 の変形です。
代码清单 6-2
@Sharable
public class SimpleDiscardHandler extends SimpleChannelInboundHandler<Object> {
@Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) {
// No need to do anything special
}
}
SimpleChannelInboundHandler はリソースを自動的に解放するため、将来の使用のためにメッセージへの参照を保存しないでください。これらの参照は無効になるためです。
セクション 6.1.6 では、参照処理について詳しく説明します。
4 ChannelOutboundHandler
アウトバウンド操作とデータは ChannelOutboundHandler によって処理されます。そのメソッドは、Channel、ChannelPipeline、および ChannelHandlerContext によって呼び出されます。
4.1 必要に応じて業務やイベントを延期する
ChannelOutboundHandler の強力な機能。これにより、複雑な方法でリクエストを処理できるようになります。リモート ノードへの書き込みが一時停止されている場合は、フラッシュ操作を延期して、後で再開することができます。
はい、Netty の ChannelOutboundHandler には操作やイベントを延期する機能があります。これは通常、ChannelHandlerContext の write メソッドと flash メソッドを通じて実現されます。
例
ChannelOutboundHandler を使用してフラッシュ操作を延期する方法を示します。
public class MyOutboundHandler extends ChannelOutboundHandlerAdapter {
private boolean isWritePending = false;
// 当write方法被调用时,它将isWritePending标记设置为true,并调用ctx.write
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
isWritePending = true;
ctx.write(msg, promise);
}
// 当flush方法被调用时,如果isWritePending标记为true,则将它设置为false,并调用ctx.flush
@Override
public void flush(ChannelHandlerContext ctx) throws Exception {
if (isWritePending) {
isWritePending = false;
ctx.flush();
}
}
}
より複雑な操作のために必要に応じて変更できます。
4.2 ChannelOutboundHandler API
表 6-4 に、ChannelOutboundHandler 自体によって定義されたすべてのメソッドを示します (ChannelHandler から継承されたメソッドは無視します)。
4.3 ChannelPromise VS ChannelFuture
ChannelOutboundHandler のほとんどのメソッドは、操作の完了時に通知を受ける ChannelPromise パラメーターを受け取ります。ChannelPromise は ChannelFuture のサブクラスであり、setSuccess() や setFailure() などのいくつかの書き込み可能なメソッドを定義しているため、ChannelFuture は不変になります。ここで参照するのは、Scala の Promise と Future の設計です。Promise が履行されると、対応する Future の値はいかなる方法でも変更できません。
5チャンネルハンドラーアダプター
5.1 重要性
ChannelHandler を作成するタスクを簡素化するクラス。
ChannelInboundHandlerAdapter クラスと ChannelOutboundHandlerAdapter クラスを独自の ChannelHandler の開始点として使用できます。これら 2 つのアダプターは、それぞれ ChannelInboundHandler と ChannelOutboundHandler の基本実装を提供します。抽象クラス ChannelHandlerAdapter を拡張することで、共通の親インターフェイス ChannelHandler のメソッドを取得します。
図 6-2 ChannelHandlerAdapter クラスの階層:
ChannelHandlerAdapter は、ユーティリティ メソッド isSharable() も提供します。対応する実装が Sharable としてマークされている場合、このメソッドは true を返し、複数の ChannelPipeline に追加できることを示します (セクション 2.3.1 で説明したとおり)。
ChannelInboundHandlerAdapter および ChannelOutboundHandlerAdapter によって提供されるメソッド本体は、関連付けられた ChannelHandlerContext で同等のメソッドを呼び出して、ChannelPipeline 内の次の ChannelHandler にイベントを転送します。
これらのアダプター クラスを独自の ChannelHandler で使用するには、カスタム実装を必要とするメソッドを拡張してオーバーライドするだけです。
6 リソース管理
データを処理するために次のメソッドを呼び出すときは常に、リソース リークがないことを確認する必要があります。
- ChannelInboundHandler.channelRead()
- またはChannelOutboundHandler.write()
Netty は、プールされた ByteBuf の参照カウントを使用します。したがって、ByteBuf が完全に使用された後、その参照カウントを調整することが非常に重要です。潜在的な (リソース リーク) 問題の診断を支援するために、Netty は、アプリケーションのバッファ割り当ての約 1% をサンプリングしてメモリ リークを検出するクラス ResourceLeakDetector を提供します。関連するオーバーヘッドは非常に小さいです。メモリ リークが検出された場合、次のようなログ メッセージが生成されます。
LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable advanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option
'-Dio.netty.leakDetectionLevel=ADVANCED' or call
ResourceLeakDetector.setLevel().
6.1 漏れ検出レベル
Netty は現在、表 6-5 に示す 4 つのリーク検出レベルを定義しています。
タイプ | 説明 |
---|---|
無効 | 漏れ検出を無効にします。徹底的なテストを行った後にのみこの値に設定する必要があります |
単純 | デフォルトのサンプリング レート 1% を使用して、見つかった漏れを検出して報告します。これはデフォルトのレベルであり、ほとんどの状況に適しています |
高度 | デフォルトのサンプリング レートを使用して、見つかったリークと、対応するメッセージがどこにアクセスされたかを報告します。 |
パラノイア | ADVANCED と似ていますが、(メッセージへの) すべてのアクセスをサンプリングします。これはパフォーマンスに大きな影響を与えるため、デバッグ段階でのみ使用する必要があります。 |
リーク検出のレベルは、次の Java システム プロパティを表内の値に設定することで定義できます。
java -Dio.netty.leakDetectionLevel=ADVANCED
この JVM オプションを使用してアプリケーションを再起動すると、アプリケーションで最後にリークしたバッファがどこにアクセスされたかがわかります。以下は、単体テストによって生成される典型的なリーク レポートです。
Running io.netty.handler.codec.xml.XmlFrameDecoderTest
15:03:36.886 [main] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.
Recent access records: 1
\#1: io.netty.buffer.AdvancedLeakAwareByteBuf.toString( AdvancedLeakAwareByteBuf.java:697)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml( XmlFrameDecoderTest.java:157)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages( XmlFrameDecoderTest.java:133)
...
ChannelInboundHandler.channelRead()、ChannelOutboundHandler.write() メソッドを実装するときに、この診断ツールを使用してリークを防ぐにはどうすればよいですか? channelRead() オペレーションが受信メッセージを直接消費しているかどうか、つまり、ChannelHandlerContext.fireChannelRead() メソッドを呼び出して受信メッセージを次の ChannelInboundHandler に転送していないかどうかを確認します。コード リスト 6-3 は、メッセージを解放する方法を示しています。
@Sharable
public class DiscardInboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 释放资源
ReferenceCountUtil.release(msg);
}
}
6.2 受信メッセージを消費する簡単な方法
受信データの消費は日常的なタスクであるため、Netty は SimpleChannelInboundHandler の特別な ChannelInboundHandler 実装を提供します。メッセージが channelRead0() メソッドによって消費された後、実装はメッセージを自動的に解放します。
受信データの消費とは、受信したネットワーク データを Netty アプリケーションで処理することを指します。クライアントがサーバーにデータを送信すると、サーバーはデータを受信して読み取ります。これらのデータは、外部ネットワークからサーバーに流入するため、受信データです。
Netty の受信データは通常、ChannelInboundHandler によって処理されます。これらのハンドラーは、受信したデータをデコードし、アプリケーションが理解できる形式に変換し、次のハンドラーまたはアプリケーション自体に渡す役割を果たします。
受信データの消費ステップ
- データの読み取り: ChannelHandlerContext#read を使用してネットワークからデータを読み取ります
- データのデコード: ChannelInboundHandlerAdapter#channelRead を使用して、読み取りデータをデコードします。
- データの処理: ビジネス ロジック ハンドラーを使用してデコードされたデータを処理する
- データを渡す: ChannelHandlerContext#fireChannelRead を使用して、処理されたデータを次のハンドラーまたはアプリケーション自体に渡します。
アウトバウンド方向では、write() 操作を処理してメッセージを破棄する場合は、メッセージを解放する責任も必要です。リスト 6-4 は、書き込まれたすべてのデータを破棄する実装を示しています。
@Sharable
public class DiscardOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
// 使用 ReferenceCountUtil.realse(...)释放资源
ReferenceCountUtil.release(msg);
promise.setSuccess();
}
}
リソースを解放するだけでなく、ChannelPromise にも通知します。そうしないと、ChannelFutureListener が特定のメッセージが処理されたという通知を受信できない可能性があります。
つまり、メッセージが消費または破棄され、ChannelPipeline 内の次のメッセージに配信されなかった場合
ChannelOutboundHandler の場合、ReferenceCountUtil.release() を呼び出すのはユーザーの責任です。メッセージが実際のトランスポート層に到達すると、メッセージが書き込まれたとき、またはチャネルが閉じられたときに自動的に解放されます。