高い同時実行性を効率的に処理する netty を使用してファイル転送プロキシを構築するプロジェクトを行っています。
これが私の構造です:
Back Server、netty.io の Http(File Server) の例と同様の通常のファイル サーバーで、要求を受信して確認し、ChunkedBuffer またはゼロコピーを使用してファイルを送信します。
NioServerSocketChannelFactory と NioClientSocketChannelFactory の両方を備えたProxy 。どちらも cachedThreadPool を使用し、クライアントのリクエストをリッスンし、バック サーバーからクライアントにファイルをフェッチします。新しいクライアントが受け入れられると、新しく受け入れられた Channel(channel1) が NioServerSocketChannelFactory によって作成され、リクエストを待機します。リクエストが受信されると、プロキシはNioClientSocketChannelFactory を使用してバック サーバーへの新しい接続を確立し、新しいチャネル (channel2) がバック サーバーにリクエストを送信し、クライアントにレスポンスを配信します。独自のパイプラインを使用する各 channel1 と channel2。
もっと簡単に言えば、手続きは
チャンネル1が受け入れられました
channel1 がリクエストを受け取ります
バックサーバーに接続された channel2
channel2 はバックサーバーにリクエストを送信します
channel2 はバックサーバーからの応答 (ファイルを含む) を受け取ります
channel1 は、channel2 から取得した応答をクライアントに送信します。
転送が完了すると、チャネル 2 が閉じられ、チャネル 1 がフラッシュ時に閉じられます (各クライアントは 1 つの要求のみを送信します)。
必要なファイルが大きくなる可能性があるため (10M)、netty.io のプロキシ サーバーの例と同様に、channel1 が書き込み可能でない場合、プロキシは channel2.readable を停止します。
上記の構造では、各クライアントは 1 つの受け入れられたチャンネルを持ち、リクエストを送信すると、転送が完了するまで 1 つのクライアント チャンネルに対応します。
次に、ab(Apache ベンチ) を使用してプロキシへの何千ものリクエストを起動し、リクエスト時間を評価します。Proxy、Back Server、および Client は、他のトラフィックがロードされていない 1 つのラック上の 3 つのボックスです。
結果は奇妙です:
ファイル サイズ 10MB、同時実行数が 1 の場合、接続遅延は非常に小さいですが、同時実行数が 1 から 10 に増加すると、上位 1% の接続遅延が最大 3 秒と非常に大きくなります。残りの 99% は非常に小さいです。同時実行数が 20 に増えると、1% が 8 秒になります。また、同時実行数が 100 を超えると、ab がタイムアウトすることさえあります。通常、90% の処理遅延は同時実行数に比例しますが、1% は、同時実行数が乱数になると異常に高くなる可能性があります (複数のテストによって異なります)。
ファイル サイズ 1K、同時実行数が 100 未満の場合はすべて問題ありません。
それらを単一のローカルマシンに配置し、接続の遅延はありません。
誰でもこの問題を説明して、どの部分が間違っているか教えてもらえますか? オンラインで多くのベンチマークを見ましたが、それらは純粋な ping-pang テストであり、この大きなファイルの転送やプロキシに関するものではありません。これがあなたたちに興味を持ってくれることを願っています:)
ありがとうございました!
================================================== ======================================
今日、ソース コーディングを読んだ後、1 つの場所で新しいソケットが受け入れられない可能性があることがわかりました。NioServerSocketChannelSink.bind() では、boss executor が Boss.run() を呼び出します。これには、着信ソケットを受け入れるための for ループが含まれています。このループの各反復では、受け入れられたチャネルを取得した後、AbstractNioWorker.register() が呼び出され、ワーカー エグゼキューターで実行されているセレクターに新しいソケットを追加すると想定されます。ただし、register() では、worker executor を呼び出す前に startStopLock というミューテックスをチェックする必要があります。この startStopLock は、AbstractNioWorker.run() および AbstractNioWorker.executeInIoThread() でも使用され、どちらもワーカー スレッドを呼び出す前にミューテックスをチェックします。つまり、startStopLock は 3 つの関数で使用されます。AbstractNioWorker.register() でロックされている場合、Boss の for ループ。run() がブロックされ、着信受け入れの遅延が発生する可能性があります。これが助けになることを願っています。