WCF HttpTransport: потоковый и буферизованный TransferMode - PullRequest
20 голосов
/ 28 октября 2010

У меня есть собственная служба WCF (платформа v4), которая предоставляется через пользовательскую привязку на основе HttpTransport.Привязка использует пользовательский MessageEncoder, который в значительной степени равен BinaryMessageEncoder с добавлением функции сжатия gzip.

Silverlight и клиент Windows используют веб-службу.

Проблема : в некоторых случаях службе приходилось возвращать очень большие объекты и иногда выдавать исключения OutOfMemory при ответе на несколько одновременных запросов (даже если диспетчер задач сообщил о ~ 600 МБ для процесса).Исключение произошло в пользовательском кодировщике, когда сообщение собиралось сжать, но я считаю, что это был только симптом, а не причина.Исключение гласило: «не удалось выделить x Мб», где 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);

Затем я провел эксперимент: я изменил TransferMode с Buffered на 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 и т. Д., Как вы обычно делаете для потоковых сообщений.Я все еще использую свои сложные классы с тем же атрибутом DataContract и DataMember. Я только что изменил конечную точку , вот и все.

Я подумал, что установка TransferMode - это просто способ включить потоковую передачу для правильно сформированных контрактов, но, очевидно, есть больше, чемтот.Кто-нибудь может объяснить, что на самом деле происходит под капотом, когда вы меняете TransferMode?

Ответы [ 4 ]

19 голосов
/ 01 августа 2011

Поскольку вы используете 'GZipMessageEncodingBindingElement', я предполагаю, что вы используете образец MS GZIP.

Посмотрите на DecompressBuffer() в GZipMessageEncoderFactory.cs, и вы поймете, что происходит в режиме с буферизацией.

Для примера, скажем, у вас есть сообщение несжатого размера 50M, сжатого размера 25M.

DecompressBuffer получит параметр «ArraySegment buffer» размером (1) 25M . Затем метод создаст MemoryStream, распакует в него буфер, используя (2) 50M . Затем он выполняет MemoryStream.ToArray (), копируя буфер потока памяти в новый (3) 50M массив больших байтов. Затем он берет другой байтовый массив из BufferManager AT LEAST (4) 50M + , в действительности он может быть намного больше - в моем случае это всегда было 67M для массива 50M.

В конце DecompressBuffer, (1) будет возвращено в BufferManager (который, кажется, никогда не очищается WCF), (2) и (3) подчиняются GC (который является асинхронным, и если вы быстрее чем GC, вы можете получить исключения OOM, даже если при очистке будет достаточно памяти). (4) предположительно будет возвращено BufferManager в вашем BinaryMessageEncodingBindingElement.ReadMessage ().

Подводя итог, можно сказать, что для вашего сообщения 50M ваш буферизованный сценарий временно займет 25 + 50 + 50 +, например. 65 = 190M памяти, часть из которых подвергается асинхронному сборщику мусора, часть управляется BufferManager, что, в худшем случае, означает, что в памяти хранится много неиспользуемых массивов, которые невозможно использовать в последующем запросе (например, слишком мало) ) и не имеет права на GC. Теперь представьте, что у вас есть несколько одновременных запросов, в этом случае BufferManager создаст отдельные буферы для всех одновременных запросов, которые не будут очищены никогда , если только вы вручную не вызовете BufferManager.Clear (), и я не знаю способа сделать это с помощью менеджеров буфера, используемых WCF, см. также этот вопрос: Как я могу предотвратить использование BufferManager / PooledBufferManager в моем клиентском приложении WCF тратой памяти? ]

Обновление: После миграции на IIS7 Сжатие Http ( wcf условное сжатие ) Потребление памяти, загрузка процессора и время запуска упали (не имеют чисел удобно), а затем выполнить миграцию из буферизованного в потоковый режим TransferMode ( Как я могу предотвратить использование BufferManager / PooledBufferManager в моем клиентском приложении WCF тратой памяти? ) потребление памяти моим клиентским приложением WCF сократилось с 630M (пик ) / 470М (непрерывный) до 270М (как пиковый, так и непрерывный) !

9 голосов
/ 28 октября 2010

У меня был некоторый опыт работы с WCF и потоковой передачей.

По сути, если вы не установите TransferMode в потоковое состояние, то по умолчанию оно будет буферизовано. Поэтому, если вы отправляете большие фрагменты данных, он собирается накапливать данные на вашем конце в памяти, а затем отправлять их, как только все данные загружены и готовы к отправке. Вот почему вы получаете ошибки памяти, потому что данные были очень большими и больше, чем память вашей машины.

Теперь, если вы используете потоковую передачу, он сразу же начнет отправлять куски данных на другую конечную точку вместо того, чтобы буферизовать их, что делает использование памяти очень минимальным.

Но это не значит, что приемник также должен быть настроен на потоковую передачу. Они могут быть настроены для буферизации и столкнутся с той же проблемой, что и отправитель, если у них недостаточно памяти для ваших данных.

Для достижения наилучших результатов обе конечные точки должны быть настроены для обработки потоковой передачи (для больших файлов данных).

Обычно для потоковой передачи вы используете MessageContracts вместо DataContracts, поскольку это дает вам больший контроль над структурой SOAP.

См. Эти статьи MSDN по MessageContracts и Datacontracts для получения дополнительной информации. А вот больше информации о Buffered vs Streamed .

1 голос
/ 25 июня 2014

Я думаю (и я могу ошибаться), что, ограничивая пользователей только параметром Stream в контрактах операций, которые используют режим передачи Streamed, происходит из-за того, что WCF помещает потоковые данные в раздел тела сообщения SOAPи начинает передавать его, когда пользователь начинает читать поток.Поэтому я думаю, что им было бы сложно объединить произвольное количество потоков в один поток данных.например, предположим, что у вас есть контракт операции с 3 параметрами потока, и три разных потока на клиенте начинают читать из этих трех потоков.Как вы могли бы сделать это, не используя какой-либо алгоритм и дополнительное программирование для мультиплексирования этих трех разных потоков данных (которых у WCF сейчас нет)

Что касается вашего другого вопроса, трудно сказать, что на самом деле происходит, не видя вашегополный код, но я думаю, что с помощью gzip вы фактически сжимаете все данные сообщения в байтовый массив, передавая их WCF и на стороне клиента, когда клиент запрашивает сообщение SOAP, основной канал открывает поток для чтениясообщение и канал WCF для потоковой передачи, начинает потоковую передачу данных, как это было в теле сообщения.

В любом случае, вы должны заметить, что установка атрибута MessageBodyMember просто сообщает WCF, что этот элемент должен передаваться как тело SOAP, но когда вы используете собственный кодировщик и привязку, исходное сообщение будет в основном на ваш выбор.

0 голосов
/ 10 августа 2015

Буферизовано: Перед загрузкой / загрузкой необходимо поместить весь файл в память.этот подход очень полезен для безопасной передачи небольших файлов.

Потоковый: файл может быть передан в виде фрагментов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...