Поделиться через


Большие данные и потоковая передача

Windows Communication Foundation (WCF) — это инфраструктура связи на основе XML. Так как XML-данные обычно кодируются в стандартном текстовом формате, определенном в спецификации XML 1.0, подключенные системы разработчики и архитекторы обычно обеспокоены объемом (или размером) сообщений, отправленных по сети, а кодировка на основе текста XML представляет особые проблемы для эффективной передачи двоичных данных.

Основные рекомендации

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

Кодирование данных: текст и двоичный файл

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

Хотя многие из этих и аналогичных проблем являются допустимыми, фактическое различие между сообщениями в кодировке XML в среде веб-служб XML и двоичными сообщениями в устаревшей среде удаленного вызова процедур (RPC) часто гораздо меньше, чем первоначальное рассмотрение может предложить.

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

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

Тем не менее, для определенных типов данных, таких как числа, может быть недостаток использования фиксированного размера, двоичных числовых представлений, таких как 128-разрядный десятичный тип вместо обычного текста, так как представление обычного текста может быть несколько байтов меньше. Текстовые данные могут также иметь преимущества в размере благодаря обычно более гибким вариантам кодирования текста для XML, в то время как некоторые двоичные форматы могут по умолчанию использовать 16-битный или даже 32-битный Юникод, что не применимо к двоичному формату XML .NET.

В результате выбор между текстом или двоичным файлом не так просто, как предполагается, что двоичные сообщения всегда меньше, чем текстовые сообщения XML.

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

Двоичное содержимое

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

В строке, закодированной в Base64, каждый символ представляет 6 битов исходных 8-битовых данных, что приводит к отношению 4:3 по избыточности кодирования в Base64, не учитывая дополнительные символы форматирования (возврат каретки и перевод строки), которые обычно добавляются по соглашению. Хотя значение различий между XML и двоичными кодировками обычно зависит от сценария, размер больше 33% при передаче полезных данных размером 500 МБ обычно не допускается.

Чтобы избежать этой нагрузки на кодировку, стандарт "Механизм оптимизации передачи сообщений" (MTOM) позволяет использовать внешние элементы больших данных, содержащиеся в сообщении, и переносить их в виде двоичных данных без специальной кодировки. С помощью MTOM сообщения обмениваются аналогичным образом с сообщениями электронной почты simple Mail Transfer Protocol (SMTP) с вложениями или внедренным содержимым (изображения и другое внедренное содержимое); Сообщения MTOM упаковываются в виде многопартийных и связанных последовательностей MIME с корневой частью, являясь фактическим сообщением SOAP.

Сообщение SOAP MTOM изменяется из своей некодированной версии, чтобы специальные теги элементов, ссылающиеся на соответствующие части MIME, были заменены исходными элементами сообщения, содержащими двоичные данные. В результате сообщение SOAP ссылается на двоичное содержимое, указывая на части MIME, отправляемые с ним, но в противном случае просто содержит xml-текстовые данные. Так как эта модель тесно согласуется с хорошо установленной моделью SMTP, существует широкая поддержка кодирования и декодирования сообщений MTOM на многих платформах, что делает его чрезвычайно совместимым выбором.

Тем не менее, как и в формате Base64, MTOM также сопровождается определенными накладными расходами для формата MIME, поэтому преимущества использования MTOM видны только тогда, когда размер двоичного элемента данных превышает примерно 1 КБ. Из-за накладных расходов сообщения, закодированные MTOM, могут быть больше сообщений, использующих кодировку Base64 для двоичных данных, если двоичные полезные данные остаются под этим пороговым значением. Дополнительные сведения см. в разделе "Кодировки" далее в этом разделе.

Содержимое больших данных

Опустив в сторону платформу проводного подключения, объем передачи данных в 500 МБ также создает большую локальную проблему для службы и клиента. По умолчанию WCF обрабатывает сообщения в буферизованном режиме. Это означает, что весь контент сообщения присутствует в памяти до его отправки или после получения. Хотя это хорошая стратегия для большинства сценариев и необходима для таких функций обмена сообщениями, как цифровые подписи и надежная доставка, большие сообщения могут исчерпать ресурсы системы.

Стратегия работы с большими нагрузками — потоковое вещание. Хотя сообщения, особенно выраженные в XML, обычно считаются относительно компактными пакетами данных, сообщение может быть несколькими гигабайтами размера и напоминает непрерывный поток данных больше, чем пакет данных. Когда данные передаются в режим потоковой передачи вместо буферизованного режима, отправитель делает содержимое текста сообщения доступным получателю в виде потока, а инфраструктура сообщений постоянно пересылает данные от отправителя к получателю по мере его доступности.

Наиболее распространенный сценарий, в котором происходят такие передачи содержимого больших данных, являются передачи двоичных объектов данных, которые:

  • Невозможно легко разбить на последовательность сообщений.

  • Необходимо своевременно доставить.

  • Не доступны в полном объеме при инициировании передачи.

Для данных, не имеющих этих ограничений, обычно лучше отправлять последовательности сообщений в пределах сеанса, чем одно большое сообщение. Дополнительные сведения см. в разделе "Потоковая передача данных" далее в этом разделе.

При отправке больших объемов данных необходимо задать maxAllowedContentLength параметр IIS (дополнительные сведения см. в разделе "Настройка ограничений запросов IIS") и maxReceivedMessageSize параметр привязки (например , System.ServiceModel.BasicHttpBinding.MaxReceivedMessageSize или MaxReceivedMessageSize). Свойство maxAllowedContentLength по умолчанию — 28,6 МБ, а maxReceivedMessageSize свойство по умолчанию — 64 КБ.

Кодировки

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

WCF включает три кодировщика и позволяет при необходимости записывать и подключать собственные кодировщики.

Каждая из стандартных привязок включает предварительно настроенный кодировщик, в котором привязки с префиксом Net* используют двоичный кодировщик (включая BinaryMessageEncodingBindingElement класс), а BasicHttpBindingWSHttpBinding классы используют кодировщик текстовых TextMessageEncodingBindingElement сообщений (с помощью класса) по умолчанию.

Элемент привязки кодировщика Описание
TextMessageEncodingBindingElement Кодировщик текстовых сообщений — это кодировщик по умолчанию для всех привязок на основе HTTP и наиболее подходящий выбор для всех пользовательских привязок, в которых взаимодействие является главным приоритетом. Этот кодировщик считывает и записывает стандартные текстовые сообщения SOAP 1.1/SOAP 1.2 без специальной обработки двоичных данных. System.ServiceModel.Channels.MessageVersion Если для свойства сообщения задано MessageVersion.Noneзначение, оболочка конверта SOAP опущена из выходных данных и сериализуется только содержимое текста сообщения.
MtomMessageEncodingBindingElement Кодировщик сообщений MTOM — это кодировщик текста, который реализует специальную обработку двоичных данных и не используется по умолчанию в любой из стандартных привязок, так как это строго служебная программа оптимизации по регистру. Если сообщение содержит двоичные данные, превышающие пороговое значение, при котором кодировка MTOM дает преимущество, данные выносятся в часть MIME, следующую за конвертом сообщения. См. раздел "Включение MTOM" далее в этом разделе.
BinaryMessageEncodingBindingElement Кодировщик двоичных сообщений — это кодировщик по умолчанию для привязок Net* и соответствующий выбор, когда обе стороны взаимодействуют на основе WCF. Кодировщик двоичных сообщений использует двоичный XML-формат .NET, специфичное для Microsoft двоичное представление для наборов данных XML (Infosets), которое обычно занимает меньше памяти, чем эквивалентное представление XML 1.0, и кодирует двоичные данные в виде потока байтов.

Кодировка текстовых сообщений обычно лучше всего подходит для любого пути обмена данными, требующего взаимодействия, а кодировка двоичных сообщений — лучший вариант для любого другого пути взаимодействия. Кодировка двоичных сообщений обычно дает меньший размер сообщения по сравнению с текстом для одного сообщения и постепенно еще меньше размеров сообщений в течение сеанса связи. В отличие от кодирования текста, двоичное кодирование не должно использовать специальную обработку двоичных данных, например использование Base64, но представляет байты в виде байтов.

Если ваше решение не требует взаимодействия, но вы по-прежнему хотите использовать транспорт HTTP, можно создать BinaryMessageEncodingBindingElement пользовательскую привязку, которая использует HttpTransportBindingElement класс для транспорта. Если ряд клиентов вашей службы требует взаимодействия, рекомендуется предоставлять параллельные конечные точки, которые предлагают подходящие варианты транспорта и кодирования для соответствующих клиентов.

Включение MTOM

Если взаимодействие является обязательным требованием, а большие двоичные данные должны быть отправлены, то кодировка сообщений MTOM является альтернативной стратегией кодирования, которую можно включить в стандарте BasicHttpBinding или WSHttpBinding привязках, задав соответствующее MessageEncoding свойство Mtom или создав его MtomMessageEncodingBindingElement в CustomBinding. В следующем примере кода, извлеченном из примера кодирования MTOM , показано, как включить MTOM в конфигурации.

<system.serviceModel>  
     …  
    <bindings>  
      <wsHttpBinding>  
        <binding name="ExampleBinding" messageEncoding="Mtom"/>  
      </wsHttpBinding>  
    </bindings>  
     …  
</system.serviceModel>  

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

Поскольку кодировщик MTOM всегда генерирует сообщение типа MIME/multi-part, закодированное в формате MTOM, независимо от того, обрабатываются ли двоичные данные как внешние, обычно следует включать MTOM только для конечных точек, которые обмениваются сообщениями, содержащими более 1 КБ двоичных данных. Кроме того, контракты служб, предназначенные для использования с конечными точками с поддержкой MTOM, должны быть ограничены указанием таких операций передачи данных. Связанные функции управления должны находиться в отдельном контракте. Это правило "только для MTOM" применяется только к сообщениям, отправленным через конечную точку с поддержкой MTOM; Кодировщик MTOM также может декодировать и анализировать входящие сообщения, отличные от MTOM.

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

Модель программирования

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

[DataContract]  
class MyData  
{  
    [DataMember]  
    byte[] binaryBuffer;  
    [DataMember]  
    string someStringData;  
}

При использовании MTOM предыдущий контракт данных сериализуется в соответствии со следующими правилами:

  • Если binaryBuffer не является null и если он по отдельности содержит достаточно данных, чтобы оправдать затраты на внешнюю обработку, связанные с MTOM (включая заголовки MIME и т. д.), по сравнению с кодировкой Base64, то данные выводятся наружу и передаются вместе с сообщением в виде двоичной части MIME. Если пороговое значение не превышается, данные кодируются как Base64.

  • Строка (и все остальные типы, которые не являются двоичными) всегда представлена как строка внутри текста сообщения независимо от размера.

Влияние на кодировку MTOM остаётся одинаковым, независимо от того, используете ли вы явный контракт данных, как показано в предыдущем примере, список параметров в операции, вложенные контракты данных или передаете объект контракта данных внутри коллекции. Массивы байтов всегда являются кандидатами для оптимизации и оптимизированы при соблюдении пороговых значений оптимизации.

Замечание

Вам не следует использовать System.IO.Stream производные типы внутри контрактов данных. Потоковая передача данных должна быть передана с помощью модели потоковой передачи, описанной в следующем разделе "Потоковая передача данных".

Интеграция

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

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

Ограничения

При включении потоковой передачи нельзя использовать значительное количество функций WCF:

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

  • Шифрование зависит от цифровых подписей, чтобы убедиться, что данные были восстановлены правильно.

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

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

Поскольку базовые транспорты NetTcpBinding и NetNamedPipeBinding имеют встроенную поддержку надежной доставки и сеансов на основе подключений, в отличие от HTTP, эти две привязки минимально затрагиваются данными ограничениями на практике.

Потоковая трансляция недоступна с транспортом очереди сообщений (MSMQ), поэтому её нельзя использовать с классом NetMsmqBinding или MsmqIntegrationBinding. Транспорт очереди сообщений поддерживает только буферные передачи данных с ограниченным размером сообщения, в то время как все остальные транспорты не имеют практических ограничений на размер сообщения для подавляющего большинства сценариев.

Потоковая передача также недоступна при использовании транспорта однорангового канала, поэтому она недоступна с NetPeerTcpBinding.

Потоковое вещание и сеансы

При потоковых вызовах с привязкой на основе сеанса может возникнуть непредвиденное поведение. Все вызовы потоковой передачи выполняются через один канал (канал диаграммы данных), который не поддерживает сеансы, даже если используемая привязка настроена для использования сеансов. Если для нескольких клиентов выполняется потоковая передача вызовов к одному объекту службы через привязку на основе сеанса, а для режима параллелизма объекта службы задано однократное значение, а в режиме контекста экземпляра задано значение PerSession, все вызовы должны проходить через канал диаграммы данных, поэтому одновременно обрабатывается только один вызов. Один или несколько клиентов могут выйти из тайм-аута. Эту проблему можно обойти, установив для объекта службы режим контекста экземпляра на PerCall или значение Concurrency на Multiple.

Замечание

MaxConcurrentSessions не действует в этом случае, так как доступен только один сеанс.

Включение потоковой передачи

Потоковую передачу можно включить следующим образом:

  • Отправлять и принимать запросы в режиме потоковой передачи и принимать и возвращать ответы в буферизованном режиме (StreamedRequest).

  • Отправлять и принимать запросы в буферизованном режиме, принимать и возвращать ответы в потоковом режиме (StreamedResponse).

  • Отправка и получение запросов и ответов в потоковом режиме в обоих направлениях. (Streamed).

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

<system.serviceModel>  
     …  
    <bindings>  
      <basicHttpBinding>  
        <binding name="ExampleBinding" transferMode="Streamed"/>  
      </basicHttpBinding>  
    </bindings>  
     …  
</system.serviceModel>  

При создании экземпляра привязки в коде необходимо задать соответствующее TransferMode свойство привязки (или элемент транспортной привязки, если вы создаете пользовательскую привязку) одному из ранее упомянутых значений.

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

Включение асинхронной потоковой передачи

Чтобы включить асинхронную потоковую передачу, добавьте DispatcherSynchronizationBehavior поведение конечной точки в узел службы и задайте для его свойства AsynchronousSendEnabled значение true. Мы также добавили возможность истинной асинхронной потоковой передачи на стороне отправки. Это повышает масштабируемость службы в сценариях, когда она транслирует сообщения нескольким клиентам, некоторые из которых медленно считывают сообщения, возможно, из-за перегрузки сети, или вообще не считывают их. В этих сценариях мы теперь не блокируем отдельные потоки в службе для каждого клиента. Это гарантирует, что служба может обрабатывать гораздо больше клиентов, тем самым повышая масштабируемость службы.

Модель программирования для потоковой передачи

Модель программирования для потоковой передачи проста. Для получения потоковых данных укажите контракт операции с одним Stream типизированным входным параметром. Чтобы вернуть потоковые данные, верните ссылку Stream.

[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples")]  
public interface IStreamedService  
{  
    [OperationContract]  
    Stream Echo(Stream data);  
    [OperationContract]  
    Stream RequestInfo(string query);  
    [OperationContract(OneWay=true)]  
    void ProvideInfo(Stream data);  
}  

Операция Echo в предыдущем примере получает и возвращает поток, поэтому её следует использовать с привязкой Streamed. Для операции RequestInfo лучше всего подходит StreamedResponse, так как она возвращает только Stream. Односторонняя операция лучше всего подходит для StreamedRequest.

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

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

[MessageContract]  
public class UploadStreamMessage  
{  
   [MessageHeader]  
   public string appRef;  
   [MessageBodyMember]  
   public Stream data;  
}

Потоковый перенос завершается и сообщение закрывается, когда поток достигает конца файла (EOF). При отправке сообщения (возврате значения или вызове операции) можно передать FileStream, и инфраструктура WCF впоследствии извлекает все данные из этого потока, пока поток не будет полностью прочитан и достигнут EOF. Для передачи потоковых данных из источника, для которого не существует предварительно созданного Stream производного класса, создайте такой класс, наложите этот класс на ваш источник потока и используйте его в качестве аргумента или возвращаемого значения.

При получении сообщения WCF создает поток через содержимое текста сообщения в кодировке Base64 (или соответствующую часть MIME при использовании MTOM), а поток достигает EOF при чтении содержимого.

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

Особые вопросы безопасности для больших данных

Все привязки позволяют ограничить размер входящих сообщений, чтобы предотвратить атаки типа "отказ в обслуживании". Например BasicHttpBinding, предоставляет свойство System.ServiceModel.BasicHttpBinding.MaxReceivedMessageSize , ограничивающее размер входящего сообщения, а также ограничивает максимальный объем памяти, доступ к которому осуществляется при обработке сообщения. Эта единица задается в байтах со значением по умолчанию 65 536 байт.

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

Поэтому ограничение максимального размера входящих сообщений в данном случае недостаточно. Свойство MaxBufferSize необходимо для ограничения объема памяти, используемой буферами WCF. Важно задать это безопасное значение (или сохранить его в значении по умолчанию) при потоковой передаче. Например, предположим, что служба должна получать файлы размером до 4 ГБ и хранить их на локальном диске. Предположим, что память ограничена таким образом, что одновременно можно буферизуть только 64 КБ данных. Затем необходимо задать MaxReceivedMessageSize значение 4 ГБ и MaxBufferSize 64 КБ. Кроме того, в реализации службы необходимо убедиться, что вы читаете только из входящего потока в блоках 64 КБ и не считываете следующий блок, прежде чем предыдущий был записан на диск и удален из памяти.

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

Замечание

Решение об использовании буферизованной или потоковой передачи является локальным решением конечной точки. Для HTTP- транспорта режим передачи не распространяется через подключение, на прокси-серверы и другие посредники. Настройка режима передачи не отражается в описании интерфейса службы. После создания клиента WCF в службу необходимо изменить файл конфигурации для служб, предназначенных для использования с потоковой передачей для задания режима. Для транспорта TCP и именованных каналов режим передачи задается в виде утверждения политики.

См. также