Nettyは新しい接続アクセスイベントをどのように処理しますか?

 

より技術的な共有が私をフォローできます

序文

これまでの分析は、Nettyサーバーの起動プロセスから始まり、Netty-NioEventLoopの中心にまで及んでいました。また、非同期APIとNettyの設計原則を要約しています。ここで、Nettyサーバー自体に戻り、サーバーとクライアント間の新しい接続を確認します。入力処理とはどのような処理ですか?

オリジナル:ネッティーは、新しい接続のアクセスイベントを処理する方法ですか?

Java NIOは新しく接続されたエンコーディングテンプレートを処理します

まず、新しい接続アクセスの場合、NIOレベルからのマクロインプレッションがあります。

1. I / Oを介してマルチプレクサセレクタが新しいクライアント接続を検出します

Nettyに対応して、新しい接続は、NioServerSocketChannel(基になるパッケージ化されたJDKのServerSocketChannel)にバインドされたI / Oマルチプレクサー(NioEventLoopスレッドによって駆動)を介してOP_ACCEPT(= 16)イベントをポーリングします

2.新しい接続をポーリングしてクライアントチャネルを作成する

Nettyに対応するのがNioSocketChannel(下部にあるJDKをカプセル化するSocketChannel)です。

3.新しいセレクターを新しい接続に割り当てます

Nettyに対応して、2番目のスレッドプールであるワーカースレッドプールからNIO行をスレッドセレクターを介して選択し、このスレッドの新しいセレクターにJDK SocketChannelを登録するプロセスを実行して、Nettyをカプセル化します。追加オブジェクトとしてのNioSocketChannelもセレクターにバインドされています

4. I / O読み取りまたは書き込みイベントを、クライアントチャネルにバインドされたセレクターに登録します。

Nettyの設計概念は最初に読み取ることなので、Nettyに対応して、これはデフォルトの登録読み取りイベントです。将来、このチャネルの読み取りおよび書き込みイベントは、ワーカースレッドプールのNIOスレッドによって管理されます

上記の4つのステップは、実際には次のJDK NIOデモを抽象化してカプセル化し、いくつかのバグを解決するプロセスです。

次のいくつかの記事では、各ステップを徐々に分解し、Nettyのデザインのアイデアを学びます。

NettyのマルチスレッドReactorアーキテクチャの簡単なレビュー

NioEventLoopGroupはスレッドプールに対応することを以前に分析しました。NioEventLoopインスタンスはNIOスレッドに対応します。EventLoopインスタンスは、内部実行メソッドを変更しないスレッドによって駆動されます(Runnableの実行とは異なります)。

簡単に言うと、Nettyサーバーによって作成されたボスとワーカーは2つのスレッドプールです。サーバーポートの場合、NIOスレッドのみがbossGroupで開始され、そのポートでの新しいクライアント接続の検出とアクセスプロセスを処理します。

具体的には、Nettyは、サーバーのChannelのチャネルにデフォルトで新しい接続のハンドラーを作成します。これは、サーバーがクライアントの新しい接続に接続するためにのみ使用され、workerGroupに複数のNIOスレッドがあります(デフォルトではCPUの2倍)コア番号)、確立されたチャネルでの読み取りおよび書き込みイベントの検出、登録、処理、およびその他の操作を担当します。ボススレッドプールのNIOスレッドが新しい接続を検出すると、少し中断する可能性があります(または、新しい接続の検出と処理を続行します)。このとき、次の図に示すように、ワーカースレッドプールがビジー状態になります。

詳細については、Nettyのスレッドスケジューリングモデル分析(1)を参照してください。

以下は、ボススレッドとワーカースレッドプールがどのように連携するかをまとめたものです。

JDKのselectメソッドを見てください。

まとめる前に、私は個人的にJDKの選択を最初に確認する必要があると思います。I/ Oマルチプレクサーセレクター上のいわゆるポーリングを正しく理解し、準備ができているチャネルの数の真の意味を返す必要があります。つまり、このプロセスには前提があります。最後の選択から計算されています。この乾いた説明は明確ではない可能性があります。ここに例を示します。たとえば、2つの確立されたチャネルAとBがあり、AとBがセレクターに登録され、セレクターでselect()が呼び出されます。

  • select()への最初の呼び出しは、AだけがI / Oイベントの準備ができていることを見つけ、selectはすぐに1を返し、それを処理します

  • select()への2番目の呼び出しは、別のチャネルBにもI / Oイベントの準備ができていることを検出します。この時点でも、select()は1を返します。つまり、最後の選択以降に計算されています。

もう1つ注意:最初のポーリング後にAで操作がない場合は、2つの準備チャネルがあります。

さらに、selectが返された後、その戻り値によってチャネルの準備ができているかどうかを判断できることを知っておく必要があります。Readyチャネルがある場合は、selectedKeys()メソッドを使用して、Readyチャネルとその一部の属性を取得できます。selectedKeys()の使い方を見てみましょう。

Set <SelectionKey> selectedKeys = selector.selectedKeys();

チャネルをセレクターに登録すると、呼び出されたregister()メソッドは、セレクターに登録されたチャネルを表すSelectionKeyオブジェクトを返し、このコレクションをトラバースして準備ができたチャネルにアクセスできます。

上記では、以前のスレッドスケジューリングモデルが分析されています。この図を思い出してください。

詳細なレビューについては、以下を参照してください。

Nettyのスレッドスケジューリングモデル分析(2)

Nettyのスレッドスケジューリングモデル分析(3)

新しい接続アクセスイベントのNettyソースコード分析

前の記事では、NioEventLoopGroupがインスタンス化されるときに、外部構成がない場合、スレッドexecutor-ThreadPerTaskExcutor、デフォルトでNioEventLoopの配列(スレッドプール)、およびスレッドセレクターセレクターが作成されるとまとめました。

また、NioEventLoopをインスタンス化して基になるスレッド配列を埋めると、Nettyは各NioEventLoopのI / Oマルチプレクサーセレクターと非同期タスクキューMPSCQを作成してバインドし、Nettyを要約します。 NioEventLoopスレッドには、2つのトリガーの機会があります。

  • 巨視的には、サーバーがポートにバインドされると、ボススレッドプールのNIOスレッドがトリガーされ、ユーザーコードがbindメソッドを呼び出します。bindメソッドの奥深くに行くと、NIOスレッドの最初の開始の正確なタイミングは、JDKのI / Oマルチプレクサーを登録するときです。ServerSocketChannel-Nettyは、NIOスレッドを使用して、この登録ロジックを非同期タスクとしてカプセル化します。ドライバー、それが開始されていない場合は開始し、将来のチャネルバインディングポートのロジックも非同期タスクとしてカプセル化され、開始されたNIOスレッドを再利用します

  • 新しい接続により、ワーカースレッドプールのNIOスレッドが開始されます。スレッドプールのスレッドセレクターは、ワーカーのNIOスレッドを新しい接続にバインドします。最初のアクセスの後に開始するか、スレッドプールのスレッドが完全に開始されていません。

つまり、Nettyサーバーが起動した後、サーバー上のチャネルはボススレッドプールのNIOスレッドにバインドされ、OP_ACCEPTイベントが発生するかどうかを継続的に検出し、それが検出されるまで、つまりボススレッドプールのNioEventLoopを処理します。スレッドは2つのことだけを行います。

1. OP_ACCEPTイベントのポーリング

2. OP_ACCEPTイベントを検出すると、イベントが処理されます。処理プロセスは、実際にはクライアントチャネル(新規接続)アクセスのプロセスです。

以下は、NIOスレッドの開始時に実行を開始するNioEventLooスレッド実行のイベントループのコアメソッドを引き続き確認します。

これの前に、まずrunメソッドのブレークポイントを解除します。次に、実験で使用された最小バージョンのNettyサーバーのデモを開始し、次にtelnetコマンドを使用して3つの要求を3つのクライアントに順番に送信し、クライアント3の新しい接続をシミュレートします。入力の過程で、以下は実行追跡ソースコードを入力します。

 

1.最初に、Nettyによってカプセル化された選択メソッドを呼び出します。以前に分析したように、新しいクライアント接続がある場合、これはOP_ACCEPTイベントがトリガーされたことを意味します。セレクターの選択メソッドは、次のようにすぐに1を返します:

ここでは、JDKのselectメソッドの戻り値が何であるかを理解します。select()メソッドは、登録されたインタレストI / Oイベントの準備ができているチャネルの数を返します。表現は、最初に現在のI / Oマルチプレクサーに登録されているチャネルによって異なります。次に、これらのチャネルを確認します関心のあるI / Oイベントがサイトに登録されていますか?上記のコードではローカル変数selectedKeys == 1ですが、実験では3つのクライアント接続があります。疑わしいかもしれませんが、なぜselectedKeysは3ではないのですか?

現在ボススレッドにバインドされているI / Oマルチプレクサーはサーバー側のチャネルのみを登録するため、下部にServerSocketChannelが1つだけあり、現在登録されている対象のI / OイベントはOP_ACCEPTのみなので、新しい接続がいくつ接続されていても入力してください。ここでは1のみを返します。

別の誤解があります:セレクターの選択の戻り値が準備ができているチャネルの総数であると考えてはいけません。実際、それは最後のselect()呼び出し以降に準備完了状態になったチャネルの数を返します。

分析を続行:I / Oイベントに関心のあるチャネルをポーリングした後、ループを中断し、外部実行メソッドに戻って、このI / Oイベントの処理を開始します。新しく接続されたアクセスイベントの処理を次に示します。コアメソッド以前にも分析しました、それはprocessSelectedKeysです:

詳細については、以下を参照してください。

Nettyのスレッドスケジューリングモデルの分析(7)

Nettyのスレッドスケジューリングモデル分析(8)

このメソッドには2つのバリエーションがあります。前の記事でも理由を分析しました。代表的なprocessSelectedKeysOptimizedを選択しました。その中のprocessSelectedKey(key、c​​hannel)メソッドを見てください。これは、NettyがI / Oイベントを次のように処理する方法です。

 以下は、processSelectedKeyメソッドの実装です。

まず黄色の1つを見て、ServerSocketChannelのUnsafeオブジェクトを取り出します。これも前にまとめましたが、Nettyカプセル化チャネルの最下層にはUnsafeオブジェクトがバインドされています。Unsafeは内部インターフェースであり、Channelインターフェース内に集約されています。役割は、ネットワークからチャネルへの支援です。 I / Oの操作は元々Channelの内部補助クラスになるように設計されていたため、Nettyユーザーは呼び出さないでください。そのため、このクラスのAPIがすべて安全ではないというわけではなく、Unsafeという名前が付けられています。

黄色の2に進むと、現在のチャネルが開いているかどうかが判断されます。実際には、判断されたServerSocketChannelです。すべてが黄色の3の場所にスムーズに行き、おなじみのNIO APIを見ました。以下は、黄色の3の場所の背後にあるコードの山の特別な外観です。

黄色の3で、k内に設定されているreadyOpsは、チャネルが準備ができているI / O操作のセットです。OP_ACCEPTマクロは16なので、readyOps変数は16です。 

すると黄色4のif判定ロジックがすぐに実行され、readyOpsが16なのでここに判定を入力し、黄色5のコードを実行します。ここでのロジックは、理解しやすい読み取り操作です。ServerSocketChannelのacceptメソッドがNioEventLoopのrunメソッドでポーリングされると、サーバーの最初のステップは、それに対して読み取り操作を実行することです。これは自然な考え方です。これはサーバーなので、次のコードはNioMessageUnsafeインスタンスの読み取りメソッドに入ります。

黄色の1の場合、最初の保証はNioEventLoopスレッドが実行中であることであり、外部スレッドによって実行された場合は無効です。次に、デフォルトで作成されたサーバーチャネルの構成とサーバーチャネルのパイプラインを取得します。イエロー2には、RecvByteBufAllocator.Handle allocHandle変数があり、RecvByteBufアロケーターハンドルを取得します。名前が示すように、これは受信バッファーのサイズを設定することです。簡単に言えば、スペースを無駄にせず、バイナリアルゴリズムを通じて十分なサイズのバッファーを取得することですこれはパフォーマンスの最適化の戦略であり、将来的にはNettyメモリイメージを分析するときに深く掘り下げます。

黄色の2の次の行は、構成をリセットする方法です。目的は、すべての累積カウンターをリセットし、次の読み取りサイクルで読み取るメッセージ/データのバイト数の提案を提供することです。次のように、Nettyはデフォルトで一度に16の新しい接続を読み取ります。

次に、NioMessageUnsafeインスタンスのreadメソッドの読み取りを続けます。黄色の3で、do-whileループに入ります。

 

最初にdoReadMessagesメソッドを呼び出し、do-whileループで新しい各クライアント接続を読み取り、readBufコレクションを使用して新しい接続を保存します。readBufはNioMessageUnsafeクラス内の通常のArrayListです。

次に、次のようにdoReadMessagesメソッドを入力します。このメソッドの内部ロジックはおなじみのようです。

まず、JDKのNIO APIを黄色の1でカプセル化します。つまり、クライアントのソケットNIOをSocketChannelに対応させます。この操作を完了すると、TCP / IPプロトコルスタックが3つのTCPハンドシェイクを完了し、TCP論理リンクが正式に確立されます次に、黄色の2で、Nettyはクライアントチャネルを独自のクライアントチャネルであるNioSocketChannelとしてカプセル化します。サーバーが受け入れイベントを処理していることは明らかであるため、リフレクションによってNioSocketChannelを作成する必要はなく、直接インスタンス化することができます。Nettyのクライアントチャネル作成プロセスの後続の分析について詳しく説明します。最後に、カプセル化されたチャネルがArrayList readBufに保存され、doReadMessagesメソッドは1を返します。 

上のdo-whileループに戻ります。

doReadMessagesによって返されるlocalRead == 1は、新しい接続が今回正常に読み取られたことを示します。do-whileサイクルが新しい接続を読み取った後、すべての読み取りが完了するか、しきい値に達するまで、次の新しい接続を読み取り続けます。つまり、Nettyは新しい接続を読み取るときのパフォーマンスも考慮します。接続が多すぎる場合、Nettyは常にここでスタックするわけではありません。デフォルトではdo-whileループ処理16になり、このロジックは黄色5の判定条件にあります。 、しきい値を超えている場合は、do-whileを終了します。

黄色の5の判定ロジック、つまり、continueReading()メソッドを見てみましょう。

Nettyの設計コンセプトは、最初に読み取ることです。これは、OP_READイベントをサーバーチャネルに自動的に登録します。つまり、autoRead()メソッドはtrueを返します。maxMessagePerReadのデフォルト設定は16です。つまり、集中受け入れイベントが処理されるたびに、最も多くの読み取り接続が行われます。性能バランスを考慮して設計された16本で、ユーザー設定が可能です。

NioMessageUnsafeインスタンスのreadメソッドの読み取りを続行します。新しい接続がある場合は、例外が発生するか、読み取られる新しい接続の数がしきい値に達するか、読み取る新しい接続がない場合、doReadMessagesは0を返し、doを終了します。 -whileループ。ここで説明すると、通常の状況では、チャネルに準備ができたI / Oイベントがある場合、つまり読み取るデータがある場合にのみ、DoReadMessagesの受け入れがブロックしてはなりません。これは、本質的にNettyサーバーがNIOモデルは非ブロッキングI / O用に構成されています。つまり、Nettyは各チャネルを次のように自動的に構成します。

さらに、サーバーチャネルにレディI / Oイベントがある場合、NettyのクライアントチャネルNioSocketChannelをインスタンス化するときに例外が発生しない限り、accept()はクライアントチャネルを返します。

doReadMessagesが0を返す場合は、do-whileループから抜け出し、動脈のNettyパイプラインが機能します。NioMessageUnsafeインスタンスのreadメソッドの背後にあるソースコードは次のとおりです。

黄色の6で、クライアントの新しいChannel-readBufのコレクションをトラバースし、新しい接続呼び出しの各pipeline.fireChannelRead()を伝播し、サーバーチャネルのパイプラインに沿って新しい接続をそれぞれに渡します。チャネルの後続のインバウンドハンドラーと黄色の7の場所は、読み取り操作完了イベント-fireChannelReadComplete();を伝播し、フォローアップは徐々にパイプラインを分解し、詳細に分析します。

この時点で、Nettyサーバーによるクライアントの新しい接続の検出と処理のプロセスは完了しています。

まとめる

1.パフォーマンスの比較、NIOスレッドは一度に多くの新しい接続を処理できない、Nettyはデフォルトで一度に最大16を処理する

2.新しい接続トリガーと配信を読み取った後のNettyのパイプラインメカニズムと接続プロセス

3.セレクターの選択戻り値を理解する

4.同期非ブロッキング、つまりNIOモードで、acceptメソッドがブロックしない理由についての深い理解

おすすめ

転載: www.cnblogs.com/kubixuesheng/p/12723391.html