Подтвердить что ты не робот

WCF HttpTransport: потоковое vs-буферизация TransferMode

У меня есть собственная WCF-служба (v4 framework), которая открывается через пользовательскую привязку 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?

4b9b3361

Ответ 1

Как вы используете "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, некоторые из которых подвержены асинхронному GC, некоторые из которых управляются BufferManager, что в худшем случае означает, что в памяти хранится много неиспользуемых массивов, которые не могут использоваться в последующем запросе (например, слишком малый) и не имеет права на GC. Теперь представьте, что у вас несколько одновременных запросов, в этом случае BufferManager будет создавать отдельные буферы для всех одновременных запросов, которые будут очищены никогда, если вы не вручную вызовете BufferManager.Clear(), и я не знаете, как это сделать с менеджерами буферов, используемыми WCF, см. также этот вопрос: Как я могу предотвратить BufferManager/PooledBufferManager в моем клиентском приложении WCF от потери памяти? ]

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

Ответ 2

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

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

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

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

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

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

Смотрите эти статьи MSDN на MessageContracts и Datacontracts для больше информации. И вот более подробная информация о Буферизованная и потоковая передача.

Ответ 3

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

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

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

Ответ 4

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

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