web-dev-qa-db-ja.com

WCF HttpTransport:ストリーミングvsバッファTransferMode

HttpTransportベースのカスタムバインディングを通じて公開される自己ホスト型WCFサービス(v4フレームワーク)があります。バインディングでは、gzip圧縮機能が追加された、ほぼMessageEncoderであるカスタムBinaryMessageEncoderを使用します。

SilverlightとWindowsクライアントがWebサービスを使用します。

問題:場合によっては、サービスが非常に大きなオブジェクトを返す必要があり、複数の同時要求に応答するときにOutOfMemory例外をスローすることがありました(タスクマネージャーがプロセスに対して〜600 Mbを報告した場合でも)。メッセージが圧縮されようとしているときに、カスタムエンコーダーで例外が発生しましたが、これは単なる症状であり、原因ではないと思います。例外は「x Mbの割り当てに失敗しました」と述べ、xは16、32、または64であり、過度に大きな量ではありませんでした。

サービスエンドポイントは次のように定義されます。

var transport = new HttpTransportBindingElement(); // quotas omitted for simplicity
var binaryEncoder = new BinaryMessageEncodingBindingElement(); // Readerquotas omitted for simplicity
var customBinding = new CustomBinding(new GZipMessageEncodingBindingElement(binaryEncoder), transport);

次に、実験を行いました。TransferModeBufferedからStreamedResponseに変更しました(それに応じてクライアントを変更しました)。これは新しいサービス定義です。

var transport = new HttpTransportBindingElement()
{
    TransferMode = TransferMode.StreamedResponse // <-- this is the only change
};
var binaryEncoder = new BinaryMessageEncodingBindingElement(); // Readerquotas omitted for simplicity
var customBinding = new CustomBinding(new GZipMessageEncodingBindingElement(binaryEncoder), transport);

魔法のように、OutOfMemory例外はもうありません。小さなメッセージの場合、サービスは少し遅くなりますが、メッセージのサイズが大きくなるにつれて、その差はますます小さくなります。動作(速度とOutOfMemory例外の両方)は再現可能です。両方の構成でいくつかのテストを行いましたが、これらの結果は一貫しています。

問題は解決しましたが、ここで何が起こっているのか説明できません。私が驚いたのは契約を一切変更しなかったである。つまりストリーミングメッセージに対して通常行うように、Streamパラメータなどを1つ持つコントラクトは作成しませんでした。同じDataContract属性とDataMember属性を持つ複雑なクラスをまだ使用しています。 エンドポイントを変更しました、それだけです。

TransferModeの設定は、適切に形成されたコントラクトのストリーミングをenableするための単なる方法であると思いましたが、明らかにそれ以上のものがあります。 TransferModeを変更すると、実際に内部で何が起きるかを誰かが説明できますか?

21

「GZipMessageEncodingBindingElement」を使用しているので、MS GZIPサンプルを使用していると思います。

GZipMessageEncoderFactory.csのDecompressBuffer()を確認すると、バッファモードで何が行われているのかがわかります。

例として、非圧縮サイズ50M、圧縮サイズ25Mのメッセージがあるとします。

DecompressBufferは、(1)25Mサイズの「ArraySegmentバッファ」パラメータを受け取ります。次に、メソッドは(2)50Mを使用して、MemoryStreamを作成し、バッファを解凍します。次に、MemoryStream.ToArray()を実行して、メモリストリームバッファを新しい(3)50Mの大きなバイト配列にコピーします。次に、AT LEAST(4)50M +のBufferManagerから別のバイト配列を取得しますが、実際にはもっとたくさん-私の場合、50Mアレイでは常に67Mでした。

DecompressBufferの最後に、(1)はBufferManagerに返されます(WCFによってクリアされないようです)、(2)および(3)はGCの対象です(非同期であり、GCよりも高速である場合) 、クリーンアップすると十分なメモリがある場合でも、OOM例外が発生する可能性があります)。 (4)はおそらくBinaryMessageEncodingBindingElement.ReadMessage()でBufferManagerに返されます。

要約すると、50Mメッセージの場合、バッファリングされたシナリオは一時的に25 + 50 + 50 +を占めます。 65 = 190Mメモリ、一部は非同期GCの対象、一部はBufferManagerによって管理されます。これは、最悪の場合、メモリ内で使用できない多数の未使用の配列をメモリに保持することを意味します。後続のリクエスト(例:小さすぎる)またはGCの対象外。複数の同時リクエストがあると想像してください。その場合、BufferManagerはすべての同時リクエストに対して個別のバッファを作成します。手動で呼び出さない限り、バッファは決してクリーンアップされません BufferManager.Clear()、およびWCFで使用されるバッファーマネージャーでこれを行う方法がわかりません。この質問も参照してください。 WCFクライアントアプリのBufferManager/PooledBufferManagerがメモリを浪費しないようにするにはどうすればよいですか? ? ]

更新:IIS7 Http圧縮への移行後( wcf条件付き圧縮メモリ消費、 cpuの負荷と起動時間が削除され(数値が手元にない)、その後、バッファリングされたTransferModeからストリーミングされたTransferModeに移行します( WCFクライアントアプリのBufferManager/PooledBufferManagerを防ぐ方法メモリを浪費していますか?WCFクライアントアプリのメモリ消費量が630M(ピーク)/ 470M(連続)から270M(ピークと連続の両方)に減少しました

20

私はWCFとストリーミングの経験がありました。

基本的に、TransferModeをストリーミングに設定しない場合、デフォルトでバッファリングされます。したがって、大量のデータを送信する場合は、データをメモリに蓄積し、すべてのデータが読み込まれて送信の準備ができたら送信します。データが非常に大きく、マシンのメモリよりも大きいため、メモリ不足エラーが発生したのはこのためです。

ストリーミングを使用すると、バッファリングする代わりに、すぐにデータのチャンクを他のエンドポイントに送信し始め、メモリ使用量を最小限に抑えます。

ただし、これは、レシーバーもストリーミング用に設定する必要があることを意味するものではありません。それらはバッファするように設定することができ、データに十分なメモリがない場合に送信者が行ったのと同じ問題が発生します。

最良の結果を得るには、ストリーミングを処理するように両方のエンドポイントをセットアップする必要があります(大きなデータファイルの場合)。

通常、ストリーミングでは、MessageContractsの代わりにDataContractsを使用します。これは、SOAP構造をより詳細に制御できるためです。

詳細については、MSDNの記事 MessageContracts および Datacontracts を参照してください。そして、これが バッファ型vsストリーム型 に関する詳細です。

12
Bryan Denny

ユーザーをStream転送モードを使用する操作コントラクトのStreamedパラメータだけに制限することは、WCFがストリームデータをbodyセクションに配置することから来ていると思います(私は間違っているかもしれません) of SOAPメッセージであり、ユーザーがストリームの読み取りを開始するとメッセージの転送を開始します。したがって、単一のデータフローで任意の数のストリームを多重化することは困難でした。たとえば、クライアント上の3つのストリームパラメーターと3つの異なるスレッドで操作コントラクトがあり、これらの3つのストリームから読み取りを開始するとします。これらの3つの異なるデータフローを多重化するアルゴリズムと追加のプログラミングを使用せずにこれを行うにはどうすればよいですか(WCFには現時点ではありません) )

他の質問については、完全なコードを確認せずに実際に何が行われているのかを理解することは困難ですが、gzipを使用することで、実際にすべてのメッセージデータをバイト配列に圧縮し、WCFとクライアントに渡していると思います側では、クライアントがSOAPメッセージを要求すると、基になるチャネルがメッセージを読み取るためのストリームを開き、ストリーム転送用のWCFチャネルがメッセージの本文であるので、データのストリーミングを開始します。

とにかく、MessageBodyMember属性を設定すると、このメンバーをSOAP bodyとしてストリーミングする必要があることをWCFに通知するだけですが、カスタムエンコーダーとバインディングを使用する場合、送信メッセージがどのようになるかを選択します。

1
Arashv

Buffered:アップロード/ダウンロードする前に、ファイル全体をメモリに配置する必要があります。これは、小さなファイルを安全に転送するためのアプローチです。

Streamed:ファイルはチャンク形式で転送できます。

0
user3240440