web-dev-qa-db-fra.com

WCF HttpTransport: streamé vs TransferMode tamponné

J'ai un service WCF auto-hébergé (infrastructure v4) exposé via une liaison personnalisée basée sur HttpTransport. La liaison utilise une MessageEncoder personnalisée qui est à peu près une BinaryMessageEncoder avec l’ajout de la fonctionnalité de compression gzip.

Un client Silverlight et Windows consomme le service Web.

Problème : dans certains cas, le service devait renvoyer des objets très volumineux et renvoyait parfois des exceptions OutOfMemory lorsqu’il répondait à plusieurs demandes simultanées (même si le Gestionnaire des tâches indiquait environ 600 Mo pour le processus). L'exception s'est produite dans l'encodeur personnalisé, lorsque le message était sur le point d'être compressé, mais je pense que c'était simplement un symptôme et non la cause. L'exception mentionnait "omission d'allouer x Mo" où x était égal à 16, 32 ou 64, ce qui n'est pas un montant excessivement énorme - pour cette raison, je pense que quelque chose d'autre a déjà mis le processus près d'une limite auparavant.

Le point de terminaison de service est défini comme suit:

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

Ensuite, j'ai fait une expérience: j'ai changé TransferMode de Buffered à StreamedResponse (et modifié le client en conséquence). Voici la nouvelle définition de service:

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

Comme par magie, plus aucune exception OutOfMemory . Le service est un peu plus lent pour les petits messages, mais la différence devient de plus en plus petite à mesure que la taille du message augmente. Le comportement (à la fois pour les exceptions speed et OutOfMemory) est reproductible, j'ai effectué plusieurs tests avec les deux configurations et ces résultats. sont consistant.

Problème résolu, MAIS: je ne peux pas m'expliquer ce qui se passe ici. Ma surprise vient du fait que je n’ai aucunement modifié le contrat . C'est à dire. Je n'ai pas créé de contrat avec un seul paramètre Stream, etc., comme vous le faites habituellement pour les messages en streaming. J'utilise toujours mes classes complexes avec les mêmes attributs DataContract et DataMember. Je viens de modifier le point final , c'est tout.

Je pensais que régler TransferMode n'était qu'un moyen de enable streaming pour des contrats bien formés, mais il y a évidemment plus que cela. Quelqu'un peut-il expliquer ce qui se passe réellement sous le capot lorsque vous changez TransferMode?

19

Comme vous utilisez 'GZipMessageEncodingBindingElement', je suppose que vous utilisez l'exemple MS GZIP.

Examinez DecompressBuffer() dans GZipMessageEncoderFactory.cs et vous comprendrez ce qui se passe en mode tampon.

Par exemple, supposons que vous ayez un message de taille non compressée 50M, taille compressée 25M.

DecompressBuffer recevra un paramètre 'ArraySegment buffer' de taille (1) 25M. La méthode créera ensuite un MemoryStream, y décompressera le tampon en utilisant (2) 50M. Ensuite, il effectuera un MemoryStream.ToArray (), en copiant le tampon de flux de mémoire dans un nouveau (3) 50M grand tableau d'octets. Ensuite, il faut un autre tableau d'octets dans le BufferManager de AT LEAST (4) 50M +. En réalité, cela peut être beaucoup plus - dans mon cas, il était toujours de 67M pour un tableau de 50M.

À la fin de DecompressBuffer, (1) sera renvoyé au BufferManager (qui ne semble jamais être effacé par la WCF), (2) et (3) sont soumis au GC (qui est asynchrone, et si vous êtes plus rapide que le GC , vous pouvez obtenir des exceptions dans le MOO même s’il y aurait assez de mémoire, le cas échéant). (4) sera probablement restitué au BufferManager dans votre BinaryMessageEncodingBindingElement.ReadMessage ().

En résumé, pour votre message 50M, votre scénario mis en mémoire tampon prendra temporairement 25 + 50 + 50 +, par exemple. 65 = 190M mémoire, dont une partie est soumise à une CPG asynchrone, une partie est gérée par BufferManager, ce qui - dans le pire des cas - signifie qu'il conserve en mémoire de nombreux tableaux inutilisés qui ne sont pas utilisables dans une requête ultérieure (par exemple, trop petite) ni éligible pour GC. Imaginez maintenant que vous avez plusieurs demandes simultanées. Dans ce cas, BufferManager créera des tampons distincts pour toutes les demandes simultanées, qui ne seront jamais nettoyés, sauf si vous appelez manuellement BufferManager.Clear (), et je ne sais pas de Pour faire cela avec les gestionnaires de mémoire tampon utilisés par WCF, consultez également la question suivante: Comment puis-je empêcher BufferManager/PooledBufferManager dans mon application client WCF de gaspiller de la mémoire? ]

Update: Après la migration vers IIS7 Compression Http ( compression conditionnelle de wcf ) consommation de mémoire, charge du processeur et temps de démarrage abandonnés (ne dispose pas des chiffres à portée de main), puis migration de la mémoire tampon vers la version en continu TransferMode ( Comment empêcher le gaspillage de mémoire de BufferManager/PooledBufferManager dans mon application client WCF? ) la consommation de mémoire de mon application client WCF est passée de 630 M (pic)/470M (continu) à 270 M (pic et continu)!

17
Eugene Beresovsky

J'ai eu quelques expériences avec WCF et le streaming.

Fondamentalement, si vous ne définissez pas la variable TransferMode sur streamée, la valeur par défaut est tamponnée. Ainsi, si vous envoyez des données volumineuses, les données seront accumulées en mémoire, puis envoyées une fois que toutes les données sont chargées et prêtes à être envoyées. C'est la raison pour laquelle vous avez eu des erreurs de mémoire car les données étaient très volumineuses et dépassaient la mémoire de votre ordinateur.

Désormais, si vous utilisez la diffusion en continu, l'envoi des blocs de données vers l'autre point d'extrémité commence immédiatement, au lieu de le mettre en mémoire tampon, ce qui rend l'utilisation de la mémoire très minime.

Mais cela ne signifie pas que le récepteur doit également être configuré pour la diffusion en continu. Ils pourraient être configurés pour mettre en mémoire tampon et rencontreront le même problème que l'expéditeur s'ils ne disposent pas de suffisamment de mémoire pour vos données.

Pour de meilleurs résultats, les deux points de terminaison doivent être configurés pour gérer la diffusion en continu (pour les fichiers de données volumineux).

Généralement, pour la diffusion en continu, vous utilisez MessageContracts au lieu de DataContracts car cela vous permet de mieux contrôler la structure SOAP. 

Consultez ces articles MSDN sur MessageContracts et Datacontracts pour plus d'informations. Et voici plus d'informations sur Buffered vs Streamed .

8
Bryan Denny

Je pense (et je me trompe peut-être) que limiter les utilisateurs à un paramètre Stream dans les contrats d'opération utilisant le mode de transfert Streamed provient du fait que WCF place les données de flux dans la section de corps du message SOAP et commence à transférez-le lorsque l'utilisateur commence à lire le flux. Je pense donc qu’il leur aurait été difficile de multiplexer un nombre arbitraire de flux dans un seul flux de données. Par exemple, supposons que vous ayez un contrat d'exploitation avec 3 paramètres de flux et que trois threads différents sur le client commencent à lire ces trois flux. Comment faire cela sans utiliser un algorithme et une programmation supplémentaire pour multiplexer ces trois flux de données différents (ce qui manque actuellement à la WCF)

En ce qui concerne votre autre question, il est difficile de dire ce qui se passe réellement sans voir votre code complet, mais je pense qu'en utilisant gzip, vous compressez réellement toutes les données du message dans un tableau d'octets, que vous transmettez à WCF et au client. côté, lorsque le client demande le message SOAP, le canal sous-jacent ouvre un flux pour lire le message et le canal WCF pour le transfert en flux, lance la diffusion des données comme il s’agissait du corps du message.

Quoi qu'il en soit, vous devez noter que la définition de l'attribut MessageBodyMember indique simplement à WCF que ce membre doit être diffusé en tant que corps SOAP, mais lorsque vous utilisez un encodeur personnalisé et une liaison, le choix du message sortant est généralement le vôtre.

0
Arashv