WCF HttpTransport: потоковый и буферизованный TransferMode

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

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

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

Решение

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

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

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

DecompressBuffer получит параметр 'ArraySegment buffer' размером (1) 25M. Затем метод создаст MemoryStream, распакует в него буфер, используя (2) 50M. Затем он выполнит MemoryStream.ToArray(), скопировав буфер потока памяти в новый (3) 50- байтовый массив. Затем он берет еще один байтовый массив из 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 Compression ( условное сжатие wcf) потребление памяти, загрузка процессора и время запуска упали (не иметь чисел под рукой), а затем мигрировали из буферизованного в потоковый TransferMode ( Как я могу предотвратить BufferManager / PooledBufferManager в моем WCF клиентское приложение теряет память?) потребление памяти моим клиентским приложением WCF снизилось с 630 МБ (пиковое значение) / 470 МБ (непрерывное) до 270 МБ (пиковое и непрерывное)!

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам