接続の問題を解決するために、適応バッファと読み:データを受信します
ネッティーシリーズカタログ(https://www.cnblogs.com/binarylei/p/10117436.html)
これまでのところ、我々はサービスを開始する必要があり、クライアント接続を受け取り、双方はすでに正式に通信することができます。以下の処理が要求する:データ、サービス処理、送信データを受信します。
メインラインの1分析
1.1データのヒントを読みます
私たちは次のような問題が発生したデータを受信します:
- どのようにバッファサイズを割り当てます。スペースの割り当てサイズ、小さな分布と頻繁に拡張する必要の無駄。どのように我々は、適応割当バッファサイズを達成することができますか?
- どのように高い同時実行に対処します。単一の接続が長すぎる読み取ることがあれば、同時要求の数が大幅に削減されます。我々は時間に、単一の接続プロセスを制限する必要があります。あなたが高い同時実行に対処したい場合は実際には、主要な要因は次のとおりです。各リクエストの処理時間が非常に短いです。
ネッティーは、この2つの問題を解決する方法で見てみましょう。この部分は、このセクションの内容の核心です。もちろん、送信データは、書き込みデータが多すぎて、あなたが研究の二つの部分までのデータと送信データ受信内部を比較することができますどのように、同じ問題を抱えています。
-
適応データサイズ分配(AdaptiveRecvByteBufAllocator) :
最近の要求パケットのサイズ、推測でのパケットサイズ。ByteBuf AdaptiveRecvByteBufAllocator投機:決定的還元注意を増幅する(2連続決意を必要とします)
-
連続読み出し(defaultMaxMessagesPerRead) :
接続16ごとのデフォルトは、一時的に処理されていないデータ、次への接続処理があっても、データを読み取るために接続されています。
1.2メインライン
NioEventLoop常にポーリング、イベント受信OP_READ;その後、読み出しデータがpipeline.fireChannelRead(byteBuf)を介して広がっています。
- マルチプレクサ(セレクタ)受信したイベントOP_READ
- 処理OP_READイベント:NioSocketChannel.NioSocketChannelUnsafe.read()
- バイトバッファを受信するためのデータの1024バイトの初期割り当て
- バイトのバッファからのデータを受信するためのチャンネル
- データレコードの実際のサイズは、受け入れ次のバイトのバッファサイズの割り当てを調整します
- トリガーpipeline.fireChannelRead(byteBuf)データ伝搬を読み出します
- バイトのバッファやりがいの経験受け入れるかどうかを決定します。はい、読むためにデータが存在しないまで継続しようとする、または16倍以上を、読んでの現在のラウンドの無い、終わり、次のイベントを待っているOP_READ
NioEventLoop#run
-> processSelectedKeys
-> AbstractNioMessageChannel.NioMessageUnsafe#read
-> NioServerSocketChannel#doReadMessages
-> pipeline#fireChannelRead
1.2知識ポイント
(1)データの読み出しの性質
- sun.nio.ch.SocketChannelImpl番号の読み取り(java.nio.ByteBufferの)
(2)fireChannelReadCompleteとfireChannelRead関係
- pipeline.fireChannelReadComplete():読み取りイベントは、イベントをトリガします。
- pipeline.fireChannelRead(byteBuf):各レコードのトリガーにイベントを解析します。
データは、複数のレコード、各トリガfireChannelRead事象であってもよいフェッチだけfireChannelReadCompleteトリガイベント後読み取ります。
(3)適応バッファサイズ
byteBuf AdaptiveRecvByteBufAllocator投機:決定的還元注意を増幅する(2連続決意を必要とします)
(4)高い並行処理
デフォルトでは、唯一の16倍を読むことができます。「雨が下降します」
2.ソース解析
前のセクションでは、我々はOP_READためネッティーを知っているとOP_ACCEPTイベントが統一プロセスです。受信クライアントがNioMessageUnsafe#を使用して接続されていることを除いて、データがNioByteUnsafe#を読んで使用して読み込まれ、読ま。
受信データ2.1
私たちは、NioByteUnsafe#は、このメソッドを読んで分析に焦点を当てます。ネッティー各読み出したデータは、次のステップに分割する必要があります。
- バッファを割り当てる:最近の要求、次の推測のパケットサイズに応じてパケットサイズ、その後、1024バイトをデフォルト。
- データが読み込まれません:何も言うこと、のJava NIOの基礎となるコードを直接呼び出します。
- トリガーpipeline.fireChannelRead(byteBuf) :ビジネスプロセス。
- 読み継続するかどうかを決定し次の2つの規格があり、(デフォルト16)を読み込むのいずれかが最大数を超えることはできません。第二は、データ・バッファは、すべての時間をいっぱい読んで、そのような2キロバイトByteBufの配分として、あなたは2キロバイトを読まなければなりませんデータ。
@Override
public final void read() {
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
// 每次读取数据时,都重新开始计数
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
try {
do {
// 1. 分配缓冲区,大小自适应
byteBuf = allocHandle.allocate(allocator);
// 2. 从 socket revbuf 中接收数据
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
readPending = false;
}
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
// 3. 触发事件处理
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
// 4. 判断是否继续读
} while (allocHandle.continueReading());
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
注:あなたは、適応バッファサイズの分布を見ることができますし、allocHandleに託されたデータを受信したときに、これらの2つの重要な機能を読み続けるかどうか。ネッティーはデフォルトallocHandle AdaptiveRecvByteBufAllocatorです。
doReadBytesソケットrevbufからデータを読み出すための方法が、リードバッファが満杯であるかどうかを決定するため、書き込み可能領域の各リードバッファサイズの前に必要とされ、その後、データを読み取る継続するかどうかを決定します。
// NioSocketChannel
@Override
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
// 每次读取数据前,记录缓冲区中可写区域大小,判断是否将缓冲区读满
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
2.2 AdaptiveRecvByteBufAllocator
コードを分析する前に、のの差ByteBufAllocatorとRecvByteBufAllocatorを比較してみましょう:
- ByteBufAllocator:バッファを割り当て、プールに分割することができ、バッファとバッファの直接的および間接的な2種類の非は、プールされました。デフォルトはPooledDirectByteBufです。
- AdaptiveRecvByteBufAllocator:各バッファは、バッファ割当てサイズを決定するために使用されるべきであり、読み取りを継続するかどうかをので。
AdaptiveRecvByteBufAllocatorハンドルを作成するためだけの責任、実関数は、プロセスを処理するために委任されています。関連のデフォルトの設定DefaultChannelConfigを参照してください。
(1)割り当てられたバッファ
@Override
public ByteBuf allocate(ByteBufAllocator alloc) {
return alloc.ioBuffer(guess());
}
注:あなたが見ることができる、それが直接ByteBufAllocatorに委託したバッファ割り当てます。AdaptiveRecvByteBufAllocatorのみGUESSによって()メソッドは、バッファのサイズ分布を決定します。
(2)適応バッファサイズを更新します
GUESS()メソッドが返す可変サイズをnextReceiveBufferSize、デフォルトは1024バイトです。たびに、最小64バイト、最大64キロバイトをお読みください。
static final int DEFAULT_MINIMUM = 64;
static final int DEFAULT_INITIAL = 1024;
static final int DEFAULT_MAXIMUM = 65536;
allocHandle.lastBytesReadは(doReadBytes(byteBuf))各コールデータを読んだ後、パケットのサイズは、リードバッファサイズスケーリング能力に応じて決定されるであろう。
@Override
public void lastBytesRead(int bytes) {
// attemptedBytesRead为读取前可写缓冲区大小,bytes表示当前读取的数据包大小。
// 如果二者相等,说明 socket revbuf 中还有数据可读,判断是否扩缩容
if (bytes == attemptedBytesRead()) {
// 核心方法:判断是否扩容或缩容
record(bytes);
}
super.lastBytesRead(bytes);
}
(3)適応バッファ戦略
記録方法は、コア、コンテンツポリシーバッファ伸縮算出AdaptiveRecvByteBufAllocatorあります。
レコードの分析前に方法は、我々は、バッファサイズが割り当てられている方法を見て。16バイトの容量によって膨張または収縮が512バイトよりも小さいパーティションによって512バイトバッファAdaptiveRecvByteBufAllocatorは、拡張のための512バイトは、2倍のサイズプレスまたは体積の減少よりも大きいです。すなわち、[16、32、48、...、512、1024、2048、..は、Integer.MAX_VALUE] SIZE_TABLEあり、バッファサイズがたびに配列の値を割り当てなければなりません。
private void record(int actualReadBytes) {
// 缩容
if (actualReadBytes <= SIZE_TABLE[max(0, index - INDEX_DECREMENT - 1)]) {
if (decreaseNow) {
index = max(index - INDEX_DECREMENT, minIndex);
nextReceiveBufferSize = SIZE_TABLE[index];
decreaseNow = false;
} else {
decreaseNow = true;
}
// 扩容
} else if (actualReadBytes >= nextReceiveBufferSize) {
index = min(index + INDEX_INCREMENT, maxIndex);
nextReceiveBufferSize = SIZE_TABLE[index];
decreaseNow = false;
}
}
説明:レコードのコンテンツの伸縮は、nextReceiveBufferSize値を再調整しています。
アダプティブ全体的な戦略は次のとおりです。決定的大きく、慎重に減らします。すなわち、減容条件が2回連続必要ですが、唯一の拡張時間を読む必要があります。それは、KB * 2 512もしINDEX_INCREMENT = 4、及びINDEX_DECREMENT = 1、などの512 KB、ことに留意されたい。4拡張条件が満たされ、2分の512 1は、容積減少条件を満たしています。
(4)続きを読みます
private final UncheckedBooleanSupplier defaultMaybeMoreSupplier = ()->
attemptedBytesRead == lastBytesRead;
@Override
public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) {
return config.isAutoRead() &&
(!respectMaybeMoreData || maybeMoreDataSupplier.get()) &&
totalMessages < maxMessagePerRead &&
totalBytesRead > 0;
}
説明: continueReadingデフォルトパラメータがdefaultMaybeMoreSupplierです。あなたは次のことを読み続ける場合は必要になります。
- オートリード=トゥーレ:デフォルト(DefaultChannelConfig)が真です。
- maybeMoreDataSupplier:ライト・バッファがフルであるかどうかを判断するために読んでください。満たされた場合、それはあなたが読書を続けることができ、より多くのデータがある示している可能性があり。
- maxMessagePerRead:デフォルトは16で、読み取ることが最大の回数を表します。データはtotalMessagesから読み込まれるたびに、17回以上、停止読書が増加します。接続データは、長いポイントリソースと、非常に大きいことは避けてください。
- totalBytesRead:バイトの読み取りの合計数。
少しを記録する意図は毎日ビット。おそらく、内容は重要ではありませんが、習慣は非常に重要です!