wake-up-neo.com

WCF HttpTransport: gestreamt gegen gepufferten TransferMode

Ich habe einen selbst gehosteten WCF-Dienst (v4-Framework), der über eine HttpTransport- basierte benutzerdefinierte Bindung verfügbar gemacht wird. Die Bindung verwendet eine benutzerdefinierte MessageEncoder, bei der es sich praktisch um eine BinaryMessageEncoder mit der zusätzlichen Gzip-Komprimierungsfunktion handelt.

Ein Silverlight und ein Windows-Client verbrauchen den Webdienst.

Problem : In einigen Fällen musste der Dienst sehr große Objekte zurückgeben und warf gelegentlich OutOfMemory-Ausnahmen aus, wenn auf mehrere gleichzeitige Anforderungen geantwortet wurde (selbst wenn der Task-Manager ~ 600 MB für den Prozess angab). Die Ausnahme trat im benutzerdefinierten Encoder auf, als die Nachricht kurz vor dem Komprimieren stand. Ich glaube jedoch, dass dies nur ein Symptom war und nicht die Ursache. In der Ausnahmebedingung wurde angegeben: "x MB konnte nicht zugewiesen werden", wobei x 16, 32 oder 64 war, keine übermäßig große Menge. Aus diesem Grund glaube ich, dass etwas anderes bereits den Grenzwert davor gesetzt hat.

Der Serviceendpunkt 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 machte ich ein Experiment: Ich änderte die Variable TransferMode von Buffered in StreamedResponse (und änderte den Client entsprechend). 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 mit zunehmender Nachrichtengröße immer kleiner. Das Verhalten (sowohl für Geschwindigkeits- als auch für OutOfMemory-Ausnahmen) ist reproduzierbar. Ich habe mehrere Tests mit beiden Konfigurationen und diesen Ergebnissen durchgeführt sind konsistent.

Problem gelöst, ABER: Ich kann mir nicht erklären, was hier passiert. Meine Überraschung beruht auf der Tatsache, dass ich den Vertrag in keiner Weise geändert habe . Das heißt Ich habe keinen Vertrag mit einem einzelnen Stream-Parameter usw. erstellt, wie Sie dies für gestreamte Nachrichten tun. Ich verwende immer noch meine komplexen Klassen mit demselben DataContract- und DataMember-Attribut. Ich habe gerade den Endpunkt geändert , das ist alles.

Ich dachte, das Setzen von TransferMode wäre nur ein Weg zum enable streaming für richtig gebildete Verträge, aber offensichtlich gibt es noch mehr als das. Kann jemand erklären, was unter der Haube tatsächlich passiert, wenn Sie TransferMode ändern?

19

Wenn Sie "GZipMessageEncodingBindingElement" verwenden, gehe ich davon aus, dass Sie das MS GZIP-Beispiel verwenden.

Schauen Sie sich DecompressBuffer() in GZipMessageEncoderFactory.cs an und Sie werden verstehen, was im gepufferten Modus vor sich geht.

Nehmen wir zum Beispiel an, Sie haben eine Nachricht mit einer unkomprimierten Größe von 50 MB, einer komprimierten Größe von 25 MB.

DecompressBuffer erhält einen 'ArraySegment-Puffer' von (1) 25M size. Die Methode erstellt dann einen MemoryStream, dekomprimiert den Puffer mit (2) 50M . Dann wird ein MemoryStream.ToArray () ausgeführt, der den Speicherstrompuffer in ein neues (3) 50M Big Byte-Array kopiert. Dann nimmt es ein anderes Byte-Array aus dem BufferManager von AT LEAST (4) 50M + , in Wirklichkeit kann es viel mehr sein - in meinem Fall waren es immer 67M für ein 50M-Array .

Am Ende von DecompressBuffer wird (1) an den BufferManager zurückgegeben (der scheinbar nie von WCF gelöscht wird), (2) und (3) unterliegen der GC (was asynchron ist und wenn Sie schneller als die GC sind , Sie können OOM-Ausnahmen erhalten, obwohl es genug Mem gibt, wenn Sie bereinigt werden. (4) wird vermutlich in Ihrem BinaryMessageEncodingBindingElement.ReadMessage () an den BufferManager zurückgegeben.

Um zusammenzufassen, für Ihre 50M-Nachricht wird Ihr gepuffertes Szenario vorübergehend 25 + 50 + 50 + aufnehmen, z. 65 = 190M memory, einige davon unterliegen einer asynchronen GC, einige werden vom BufferManager verwaltet. Dies bedeutet im schlimmsten Fall, dass viele ungenutzte Arrays im Speicher verbleiben, die in einer nachfolgenden Anforderung nicht verwendet werden können (z. B. zu klein) ) noch für GC geeignet. Stellen Sie sich nun vor, Sie haben mehrere gleichzeitige Anforderungen. In diesem Fall erstellt BufferManager separate Puffer für alle gleichzeitigen Anforderungen, die never bereinigt werden, es sei denn, Sie rufen BufferManager.Clear () manuell auf, und ich weiß es nicht Eine Möglichkeit, dies mit den von WCF verwendeten Puffermanagern zu tun, finden Sie auch in dieser Frage: Wie kann ich verhindern, dass BufferManager/PooledBufferManager in meiner WCF-Client-App Speicher verschwendet? ]

Update: Nach der Migration zur IIS7-HTTP-Komprimierung ( wcf-bedingte Komprimierung ) Speicherverbrauch, CPU-Auslastung und Startzeit fallen gelassen (keine Zahlen verfügbar) und anschließend migrieren vom gepufferten zum gestreamten TransferMode ( Wie kann ich verhindern, dass BufferManager/PooledBufferManager in meiner WCF-Client-App Speicher verschwendet? ) Der Speicherbedarf meiner WCF-Client-App ist von 630 MB (kontinuierlich)/470 MB (kontinuierlich) auf gefallen 270M (sowohl Peak als auch kontinuierlich) !

17

Ich habe einige Erfahrungen mit WCF und Streaming gemacht.

Wenn Sie TransferMode nicht als Streaming festlegen, wird der Wert 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 Speicherfehler bekommen, weil die Daten sehr groß waren und mehr als der Arbeitsspeicher Ihres Computers waren.

Wenn Sie nun Streaming verwenden, werden sofort Datenblöcke an den anderen Endpunkt gesendet, anstatt sie zu puffern. Dadurch wird der Speicherbedarf sehr gering.

Das heißt aber nicht, dass der Receiver auch für das Streaming eingerichtet sein muss. Sie können für das Puffern eingerichtet werden und haben das gleiche Problem wie der Absender, wenn nicht genügend Speicher für Ihre Daten vorhanden ist.

Für die besten Ergebnisse sollten beide Endpunkte so eingerichtet sein, dass Streaming (für große Datendateien) verarbeitet wird.

Normalerweise verwenden Sie für das Streaming MessageContracts anstelle von DataContracts, da Sie dadurch die Struktur SOAP besser steuern können. 

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

8
Bryan Denny

Buffered: muss die gesamte Datei vor dem Hochladen/Herunterladen im Speicher ablegen. Dieser Ansatz ist sehr nützlich, um kleine Dateien sicher zu übertragen.

Gestreamt: Die Datei kann in Form von Blöcken übertragen werden.

0
user3240440

Ich denke (und ich könnte falsch liegen), dass das Beschränken der Benutzer auf einen Parameter mit Stream in Vorgangsverträgen, die den Streamed-Übertragungsmodus verwenden, auf die Tatsache zurückzuführen ist, dass die WCF Stream-Daten in den Hauptteil der SOAP -Meldung und einfügt beginnt mit der Übertragung, sobald der Benutzer den Stream liest. Ich denke, es wäre schwierig für sie gewesen, eine beliebige Anzahl von Streams in einem einzigen Datenfluss zu multiplexen. Nehmen Sie beispielsweise an, Sie haben einen Operationsvertrag mit 3 Stream-Parametern und drei verschiedenen Threads auf dem Client, um aus diesen drei Streams zu lesen. Wie können Sie dies tun, ohne einen Algorithmus und zusätzliche Programmierung zum Multiplexen dieser drei verschiedenen Datenflüsse (die WCF derzeit fehlt) zu tun

Was Ihre andere Frage betrifft, ist es schwer zu sagen, was wirklich los ist, ohne Ihren vollständigen Code zu sehen. Wenn Sie jedoch gzip verwenden, komprimieren Sie tatsächlich alle Nachrichtendaten in einem Byte-Array und übergeben sie an WCF und auf dem Client Wenn der Client nach der SOAP- Nachricht fragt, öffnet der darunterliegende Kanal einen Stream, um die Nachricht zu lesen, und der WCF-Kanal für die gestreamte Übertragung.

Sie sollten jedoch beachten, dass die Einstellung des Attributs MessageBodyMember WCF nur sagt, dass dieses Mitglied als SOAP-Body gestreamt werden soll. Wenn Sie jedoch einen benutzerdefinierten Encoder und eine benutzerdefinierte Bindung verwenden, entscheiden Sie meistens, wie die ausgehende Nachricht aussehen wird .

0
Arashv