WCF HttpTransport: gestreamter gegen gepufferter TransferMode

Ich habe einen selbst gehosteten WCF-Dienst (v4-Framework), der über eine HttpTransport basierte benutzerdefinierte Bindung HttpTransport . Die Bindung verwendet einen benutzerdefinierten MessageEncoder , bei dem es sich im Wesentlichen um einen BinaryMessageEncoder mit der zusätzlichen BinaryMessageEncoder Komprimierungsfunktion handelt.

Ein Silverlight- und ein Windows-Client verwenden den Webdienst.

Problem : In einigen Fällen musste der Dienst sehr große Objekte zurückgeben und gab gelegentlich OutOfMemory-Ausnahmen aus, wenn er auf mehrere gleichzeitige Anforderungen reactjse (selbst wenn der Task-Manager ~ 600 MB für den process anzeigte). Die Ausnahme passierte im benutzerdefinierten Encoder, als die Nachricht komprimiert werden sollte, aber ich glaube, dies war nur ein Symptom und nicht die Ursache. Die Ausnahme besagt, dass “x Mb nicht zugeordnet werden konnte”, wobei x 16, 32 oder 64 war, keine übermäßig große Menge – aus diesem Grund glaube ich, dass etwas anderes bereits den process in die Nähe einer gewissen Grenze gebracht hat.

Der Service-Endpunkt ist wie folgt definiert:

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

Dann habe ich ein Experiment gemacht: Ich habe TransferMode von Buffered zu StreamedResponse geändert (und den Client entsprechend modifiziert). Dies ist die neue Service-Definition:

 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); 

Magisch, keine OutOfMemory-Ausnahmen mehr . Der Dienst ist für kleine Nachrichten etwas langsamer, aber der Unterschied wird immer kleiner, je größer die Nachrichtengröße ist. Das Verhalten (sowohl für die Geschwindigkeit als auch OutOfMemory-Ausnahmen) ist reproduzierbar. Ich habe mehrere Tests mit beiden Konfigurationen durchgeführt und diese Ergebnisse sind konsistent.

Problem getriggers, ABER: Ich kann mir nicht erklären, was hier passiert. Meine Überraschung rührt daher, dass ich den Vertrag in keiner Weise geändert habe . Dh ich habe keinen Vertrag mit einem einzelnen Stream Parameter usw. erstellt, wie Sie es normalerweise für gestreamte Nachrichten tun. Ich verwende immer noch meine komplexen classn mit demselben DataContract- und DataMember-Attribut. Ich habe nur den Endpunkt geändert , das ist alles.

Ich dachte, dass die Einstellung von TransferMode nur eine Möglichkeit ist, Streaming für korrekt erstellte Verträge zu ermöglichen , aber offensichtlich gibt es mehr als das. Kann jemand erklären, was tatsächlich unter der Haube passiert, wenn Sie TransferMode ändern?

Wenn Sie ‘GZipMessageEncodingBindingElement’ verwenden, nehme ich an, dass Sie das MS GZIP-Beispiel verwenden.

Werfen Sie einen Blick auf DecompressBuffer() in GZipMessageEncoderFactory.cs und Sie werden verstehen, was im gepufferten Modus vor sich geht.

Nehmen wir als Beispiel an, Sie haben eine unkomprimierte Größe 50M, komprimierte Größe 25M.

DecompressBuffer erhält einen Parameter ‘ArraySegment buffer’ mit einer Größe von (1) 25M . Die Methode erstellt dann einen MemoryStream, entpacken Sie den Puffer mit (2) 50M . Dann wird ein MemoryStream.ToArray () ausgeführt, wobei der Speicher-Stream-Puffer in ein neues (3) 50M großes Byte-Array kopiert wird. Dann braucht es ein weiteres Byte-Array vom BufferManager von AT LEAST (4) 50M + , in Wirklichkeit kann es viel mehr sein – in meinem Fall war es immer 67M für ein 50M-Array.

Am Ende von DecompressBuffer wird (1) an den BufferManager zurückgegeben (der anscheinend niemals von WCF gelöscht wird), (2) und (3) unterliegen GC (was asynchron ist und wenn Sie schneller sind als der GC) , Sie könnten OOM-Ausnahmen erhalten, obwohl es genug Speicher geben würde, wenn aufgeräumt). (4) wird vermutlich in Ihrem BinaryMessageEncodingBindingElement.ReadMessage () an den BufferManager zurückgegeben.

Zusammenfassend wird Ihr gepuffertes Szenario für Ihre 50M-Nachricht vorübergehend 25 + 50 + 50 + aufnehmen, z. B. 65 = 190M Speicher, von denen einige einem asynchronen GC unterliegen, ein Teil davon vom BufferManager verwaltet, was – Worst-Case- bedeutet Es enthält viele ungenutzte Arrays im Speicher, die weder für eine nachfolgende Anfrage (z. B. zu klein) noch für GC geeignet sind. Stellen Sie sich vor, Sie haben mehrere gleichzeitige Anfragen. In diesem Fall erstellt BufferManager separate Puffer für alle gleichzeitigen Anfragen, die niemals bereinigt werden, es sei denn, Sie rufen BufferManager.Clear () manuell auf und ich weiß nicht, wie ich das machen soll Zu den von WCF verwendeten Puffermanagern siehe auch diese Frage: Wie kann ich verhindern, dass BufferManager / PooledBufferManager in meiner WCF-Clientanwendung Speicher verschwendet? ]

Update: Nach der Migration auf IIS7 Http-Komprimierung ( wcf bedingte Komprimierung ) Speicherverbrauch, CPU laden und Start-Zeit fallen gelassen (haben Sie nicht die Nummern praktisch) und dann Migration von gepufferten zu Streaming TransferMode ( Wie kann ich BufferManager / PooledBufferManager in meinem WCF verhindern Client-App von verschwenden Speicher? ) Speicherverbrauch meiner WCF-Client-App ist von 630M (Spitze) / 470M (kontinuierlich) auf 270M (sowohl Spitze als auch kontinuierliche) gesunken !

Ich habe Erfahrung mit WCF und Streaming.

Wenn Sie den TransferMode auf Streaming setzen, wird er standardmäßig gepuffert. Wenn Sie also große Datenmengen senden, werden die Daten an Ihrem Ende im Speicher aufgebaut und dann gesendet, sobald alle Daten geladen und zum Senden bereit sind. Aus diesem Grund haben Sie keine Speichererrors mehr, da die Daten sehr groß waren und mehr als der Arbeitsspeicher Ihrer Maschine.

Wenn Sie nun Streaming verwenden, wird sofort damit begonnen, Datenstücke an den anderen Endpunkt zu senden, anstatt sie zwischenzuspeichern, wodurch die Speichernutzung sehr gering ist.

Das bedeutet aber nicht, dass der Receiver auch für das Streaming eingerichtet werden muss. Sie könnten so eingestellt werden, dass sie gepuffert werden und das gleiche Problem wie der Absender auftreten, wenn sie nicht genügend Speicher für Ihre Daten haben.

Um optimale Ergebnisse zu erzielen, sollten beide Endpunkte für das Streaming (für große Datendateien) eingerichtet werden.

In der Regel verwenden Sie für das Streaming MessageContracts anstelle von MessageContracts da Sie so mehr Kontrolle über die SOAP-Struktur erhalten.

Weitere Informationen finden Sie in den MSDN-Artikeln zu MessageContracts und Datacontracts . Und hier sind weitere Informationen über Buffered vs Streamed .

Ich denke (und ich könnte falsch liegen), dass das Beschränken von Benutzern auf einen Stream Parameter in Betriebsverträgen, die den Streamed Übertragungsmodus verwenden, von der Tatsache Streamed , dass WCF Daten im Body-Abschnitt der SOAP-Nachricht Streamed und als Übertragung überträgt Benutzer beginnt den Stream zu lesen. Also, ich denke, es wäre für sie schwierig gewesen, eine beliebige Anzahl von Streams in einem einzigen Datenstrom zu multiplexen. Angenommen, Sie haben einen Betriebsvertrag mit drei Stream-Parametern und drei verschiedene Threads auf dem Client starten, um von diesen drei Streams zu lesen. Wie können Sie das tun, ohne einen Algorithmus und zusätzliche Programmierung zu verwenden, um diese drei verschiedenen Datenflüsse zu multiplexen (was WCF momentan fehlt)

Was Ihre andere Frage anbelangt, ist es schwer zu sagen, was eigentlich vor sich geht, ohne Ihren vollständigen Code zu sehen. Aber ich denke, mit gzip komprimieren Sie alle Nachrichtendaten in ein Byte-Array und übergeben es WCF und dem Client Wenn der Client nach der SOAP-Nachricht fragt, öffnet der zugrundeliegende Kanal einen Stream zum Lesen der Nachricht und der WCF-Kanal für die gestreamte Übertragung und beginnt mit dem Streaming der Daten, da dies der Nachrichtentext ist.

Beachten Sie jedoch, dass das Festlegen des MessageBodyMember Attributs WCF lediglich mitteilt, dass dieses Mitglied als SOAP-Hauptteil gestreamt werden sollte. Wenn Sie jedoch einen benutzerdefinierten Encoder und eine Bindung verwenden, ist es meistens Ihre Entscheidung, wie die ausgehende Nachricht aussehen soll.

Gepuffert: Es muss die gesamte Datei vor dem Hoch- / Herunterladen in den Speicher geladen werden. Dieser Ansatz ist sehr nützlich, um kleine Dateien sicher zu übertragen.

Gestreamt: Die Datei kann in Form von Chunks übertragen werden.