データバッファとコーデック

Java NIO は ByteBuffer を提供しますが、多くのライブラリは、特にバッファの再利用や直接バッファの使用がパフォーマンスに有益であるネットワーク操作のために、独自のバイトバッファ API を構築します。たとえば、Netty には ByteBuf 階層があり、Undertow は XNIO を使用し、Jetty は解放されるコールバックとともにプールされたバイトバッファーを使用します。spring-core モジュールは、次のようにさまざまなバイトバッファ API を操作するための一連の抽象化を提供します。

  • DataBufferFactory は、データバッファーの作成を抽象化します。

  • DataBuffer は、プールできるバイトバッファを表します。

  • DataBufferUtils は、データバッファ用のユーティリティメソッドを提供します。

  • コーデックは、データバッファストリームをより高いレベルのオブジェクトにデコードまたはエンコードします。

DataBufferFactory

DataBufferFactory は、次の 2 つの方法のいずれかでデータバッファを作成するために使用されます。

  1. DataBuffer の実装は要求に応じて拡大および縮小できますが、既知の場合は事前に容量を指定するオプションで、新しいデータバッファーを割り当てます。

  2. 既存の byte[] または java.nio.ByteBuffer をラップします。これにより、指定されたデータが DataBuffer 実装で装飾され、割り当ては行われません。

WebFlux アプリケーションは DataBufferFactory を直接作成せず、代わりにクライアント側の ServerHttpResponse または ClientHttpRequest を介してそれにアクセスすることに注意してください。ファクトリの型は、基盤となるクライアントまたはサーバーによって異なります。Reactor Netty の場合は NettyDataBufferFactory、その他の場合は DefaultDataBufferFactory

DataBuffer

DataBuffer インターフェースは java.nio.ByteBuffer と同様の操作を提供しますが、Netty ByteBuf に触発されたいくつかの追加の利点ももたらします。以下は、利点の一部のリストです。

  • 独立した位置での読み取りと書き込み、つまり読み取りと書き込みを交互に行うために flip() を呼び出す必要はありません。

  • java.lang.StringBuilder と同様に、要求に応じて容量が拡張されました。

  • プールされたバッファーと PooledDataBuffer を介した参照カウント。

  • バッファを java.nio.ByteBufferInputStreamOutputStream として表示します。

  • 特定のバイトのインデックスまたは最後のインデックスを決定します。

PooledDataBuffer

ByteBuffer (標準 Javadoc) の Javadoc に従って、バイトバッファーは直接または非直接にできます。ダイレクトバッファは Java ヒープの外側に存在する場合があり、ネイティブ I/O 操作のためにコピーする必要がなくなります。これにより、直接バッファはソケットを介してデータを送受信するのに特に役立ちますが、作成および解放するのに費用がかかるため、バッファをプールするという考えにつながります。

PooledDataBuffer は DataBuffer の拡張であり、バイトバッファプーリングに不可欠な参照カウントを支援します。どのように機能しますか? PooledDataBuffer が割り当てられると、参照カウントは 1 になります。retain() を呼び出すとカウントが増加し、release() を呼び出すとカウントが減少します。カウントが 0 を超えている限り、バッファは解放されないことが保証されます。カウントが 0 に減少すると、プールされたバッファを解放できます。実際には、バッファの予約メモリがメモリプールに戻される可能性があります。

PooledDataBuffer を直接操作する代わりに、ほとんどの場合、PooledDataBuffer のインスタンスである場合にのみ DataBuffer にリリースまたは保持を適用する DataBufferUtils の便利なメソッドを使用することをお勧めします。

DataBufferUtils

DataBufferUtils は、データバッファを操作するためのユーティリティメソッドをいくつか提供します。

  • データバッファのストリームを、おそらくゼロコピーで単一のバッファに結合します。基になるバイトバッファー API でサポートされている場合は、複合バッファー経由。

  • InputStream または NIO Channel を Flux<DataBuffer> に、またはその逆に Publisher<DataBuffer> を OutputStream または NIO Channel に変えます。

  • バッファーが PooledDataBuffer のインスタンスである場合、DataBuffer を解放または保持するメソッド。

  • 特定のバイトカウントまでバイトストリームからスキップまたは取得します。

コーデック

org.springframework.core.codec パッケージは、次の戦略インターフェースを提供します。

  •  Publisher<T> をデータバッファのストリームにエンコードする Encoder

  • Decoder は、Publisher<DataBuffer> をより高いレベルのオブジェクトのストリームにデコードします。

spring-core モジュールは、byte[]ByteBufferDataBufferResourceString エンコーダーおよびデコーダーの実装を提供します。spring-web モジュールは、Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers、その他のエンコーダーとデコーダーを追加します。WebFlux セクションのコーデックを参照してください。

DataBuffer を使用する

データバッファーを使用する場合、バッファーがプールされる可能性があるため、バッファーが解放されるように特に注意する必要があります。コーデックを使用してその仕組みを説明しますが、概念はより一般的に適用されます。データバッファを管理するためにコーデックが内部的に行う必要があるものを見てみましょう。

Decoder は、より高いレベルのオブジェクトを作成する前に、入力データバッファーを最後に読み取るため、次のように解放する必要があります。

  1. Decoder が単に各入力バッファーを読み取り、すぐにそれを解放する準備ができている場合、DataBufferUtils.release(dataBuffer) を介して解放できます。

  2. Decoder が Flux または Mono 演算子(flatMapreduce など)を使用してデータ項目を内部でプリフェッチおよびキャッシュする場合、または filterskip などの演算子を使用して項目を除外する場合は、doOnDiscard(DataBuffer.class, DataBufferUtils::release) をコンポジションに追加する必要があります。チェーンは、おそらくエラーまたはキャンセルシグナルの結果として、そのようなバッファーが破棄される前に解放されることを保証します。

  3. Decoder が他の方法で 1 つ以上のデータバッファを保持している場合、完全に読み取られたとき、キャッシュされたデータバッファが読み取られて解放される前にエラーまたはキャンセルシグナルが発生した場合に、それらが解放されることを確認する必要があります。

DataBufferUtils#join は、データバッファストリームを単一のデータバッファに集約する安全で効率的な方法を提供することに注意してください。同様に、skipUntilByteCount と takeUntilByteCount は、デコーダーが使用する追加の安全な方法です。

Encoder は、他の人が読み取る(および解放する)必要があるデータバッファを割り当てます。Encoder にはあまり関係がありません。ただし、Encoder は、バッファーにデータを取り込む際に直列化エラーが発生した場合、データバッファーを解放するように注意する必要があります。例:

  • Java

  • Kotlin

DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
	// serialize and populate buffer..
	release = false;
}
finally {
	if (release) {
		DataBufferUtils.release(buffer);
	}
}
return buffer;
val buffer = factory.allocateBuffer()
var release = true
try {
	// serialize and populate buffer..
	release = false
} finally {
	if (release) {
		DataBufferUtils.release(buffer)
	}
}
return buffer

Encoder のコンシューマーは、受信したデータバッファーを解放する責任があります。WebFlux アプリケーションでは、Encoder の出力を使用して、HTTP サーバーレスポンスまたはクライアント HTTP リクエストに書き込みます。この場合、データバッファーの解放は、サーバーレスポンスまたはクライアントへのコード書き込みの責任です。リクエスト。

Netty で実行する場合、バッファリークのトラブルシューティング [GitHub] (英語) 用のデバッグオプションがあることに注意してください。