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

Как добиться высокопроизводительного API REST на Azure с .NET?

У нас есть веб-роль .NET, размещенная на Windows Azure, которая обслуживает REST API только с несколькими веб-методами .

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

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

До сих пор мы использовали WCF поверх форм ASP.NET с сообщениями POX (обычный статический Xml). Внешняя производительность не очень хороша, виновниками являются:

  • XML является подробным == > ограничение пропускной способности.
  • ASP.NET + WCF + WcfRestContrib медленно обрабатывает/сериализует сообщения == > Ограничение ЦП.

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

Возможные стратегии, которые я рассматриваю:

  • Отменить XML в пользу ProtoBuf.
  • Добавить upstream GZip-сжатие (классическое сжатие HTTP применяется только по течению).
  • Отменить WCF полностью в пользу raw HttpHandler s.

Кто-нибудь сравнивает различные альтернативы для достижения большей части каждой Azure VM для такого использования?

Ps: Неявно ссылаясь на Lokad Forecasting API, но попытался рассказать вопрос более общим образом.

4b9b3361

Ответ 1

В ваших POC, я думаю, вы можете удалить Azure из уравнения, когда вы тестируете некоторые из сценариев.

Если это действительно пропускная способность, сжатие, безусловно, является опцией, но это может быть проблематично, если этот веб-сервис будет открыт для "публичного", а не просто использоваться приложениями под вашим контролем. Это особенно справедливо в гетерогенной среде.

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

Я бы только запускал необработанное направление HttpHandler, если у вас есть доказательства накладных расходов WCF. Azure достаточно сложно отлаживать с таким количеством присутствия в конфиге, что я не уверен, что добавление дополнительной проблемы с сырыми HttpHandlers - правильное направление.

Ответ 2

Является ли ваш XML сериализованным через отражение (т.е. используя атрибуты и т.д.)? Если да, то protobuf-net будет намного быстрее, чем быстрее.

На самом деле, даже если ваша сериализация XML настраивается с использованием явного getter и setter Func<> s, вы все равно можете увидеть некоторое значительное преимущество с protobuf-net. В нашем случае, в зависимости от размера и содержания сериализуемых объектов, мы наблюдали увеличение скорости на 5-15% во время сериализации.

Использование protobuf-net также обеспечит пропускную способность доступной полосы пропускания, хотя это будет в значительной степени зависеть от вашего контента.

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

Ответ 3

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

Если это первый случай, то ProtoBuf действительно выглядит очень хорошим вариантом.

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

Связанные с файлом методы будут просто передавать файлы внутри тела HTTP-запросов в двоичной форме без каких-либо преобразований или кодирования. Остальные параметры будут отправляться с использованием URL-адреса запроса.

Для загрузки файлов в сервисах REST служб WCF в методе службы вам придется объявить параметр, представляющий файл типа типа Stream. Например:

[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "uploadProjectDocument?projectId={projectId}")]
void UploadProjectDocument(Guid projectId, Stream document);

Когда вы сталкиваетесь с параметрами Stream, WCF просто берет их содержимое непосредственно из тела запроса без какой-либо обработки на нем. У вас может быть только один параметр типа Stream для метода службы (что имеет смысл, потому что каждый HTTP-запрос имеет только одно тело).

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

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

Ответ 4

В этой статье http://social.msdn.microsoft.com/Forums/en-US/windowsazuredata/thread/d84ba34b-b0e0-4961-a167-bbe7618beb83 рассматриваются проблемы производительности с Azure.

Розы Azure по умолчанию запускаются только в одном потоке, который очень неэффективен на серверах. Есть несколько очень приятных шаблонов дизайна, которые показывают вам, как реализовать многопоточные роли Azure, я лично следую этому http://www.31a2ba2a-b718-11dc-8314-0800200c9a66.com/2010/12/running-multiple-threads-on-windows.html. При этом ваши роли могут последовательно сериализовать объекты.

Я использую JSON в качестве формата обмена вместо XML, он имеет гораздо меньший размер и очень хорошо поддерживается .NET 4. В настоящее время я использую DataContractJsonSerializer, но вы также можете посмотреть в JavaScriptSerializer или JSON.NET, если это производительность сериализации, после которой я предлагаю вам сравнить их.

Службы WCF по умолчанию однопоточные (источник: http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=EN-US&k=k(SYSTEM.SERVICEMODEL.SERVICEBEHAVIORATTRIBUTE.CONCURRENCYMODE);k(TargetFrameworkMoniker-%22.NETFRAMEWORK%2cVERSION%3dV4.0%22);k(DevLang-CSHARP)&rd=true). Вот пример кода, который сделает ваш RESTfull API многопоточным:

ExampleService.svc.cs

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerCall,
         IncludeExceptionDetailInFaults = false, MaxItemsInObjectGraph = Int32.MaxValue)]
    public class ExampleService : IExample

web.config

 <system.serviceModel>
    <protocolMapping>
      <add scheme="http" binding="webHttpBinding" bindingConfiguration="" />
    </protocolMapping>
    <behaviors>
      <endpointBehaviors>
        <behavior name="">
          <webHttp defaultOutgoingResponseFormat="Json" />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>  

ExampleService.svc

<%@ ServiceHost Language="C#" Debug="true" Service="WebPages.Interfaces.ExampleService" CodeBehind="ExampleService.svc.cs" %>

Кроме того, ASP.NET по умолчанию допускает только два одновременных HTTP-соединения (источник См. http://social.msdn.microsoft.com/Forums/en-US/windowsazuredata/thread/d84ba34b-b0e0-4961-a167-bbe7618beb83). Эти настройки позволят до 48 одновременных HTTP-соединений:

web.config

  <system.net>
    <connectionManagement>
      <!-- See http://social.msdn.microsoft.com/Forums/en-US/windowsazuredata/thread/d84ba34b-b0e0-4961-a167-bbe7618beb83 -->
      <add address="*" maxconnection="48" />
    </connectionManagement>
  </system.net>  

Если ваши сообщения HTTP POST обычно меньше 1460 байт, вы должны повернуть наглы для повышения производительности (source http://social.msdn.microsoft.com/Forums/en-US/windowsazuredata/thread/d84ba34b-b0e0-4961-a167-bbe7618beb83). Вот несколько настроек, которые делают это:

web.config

  <system.net>
    <settings>
      <!-- See http://social.msdn.microsoft.com/Forums/en-US/windowsazuredata/thread/d84ba34b-b0e0-4961-a167-bbe7618beb83 -->
      <servicePointManager expect100Continue="false" />
    </settings>
  </system.net>  

Определите свои JSON API следующим образом:

using System.ServiceModel;
using System.ServiceModel.Web;
using Interchange;

namespace WebPages.Interfaces
{
    [ServiceContract] 
    public interface IExample
    {
        [OperationContract]
        [WebInvoke(Method = "POST",
            BodyStyle = WebMessageBodyStyle.Bare,
            RequestFormat = WebMessageFormat.Json,
            ResponseFormat = WebMessageFormat.Json)]
        string GetUpdates(RequestUpdates name);

        [OperationContract]
        [WebInvoke(Method = "POST",
            BodyStyle = WebMessageBodyStyle.Bare,
            RequestFormat = WebMessageFormat.Json,
            ResponseFormat = WebMessageFormat.Json)]
        string PostMessage(PostMessage message);

    }
}

Вы можете сериализоваться в JSON в .NET 4 следующим образом:

string SerializeData(object data)
{
    var serializer = new DataContractJsonSerializer(data.GetType());
    var memoryStream = new MemoryStream();
    serializer.WriteObject(memoryStream, data);
    return Encoding.Default.GetString(memoryStream.ToArray());            
}

Типичный объект обмена, который вы можете определить как обычно:

using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Interchange
{
    [DataContract]
    public class PostMessage
    {
        [DataMember]
        public string Text { get; set; }

        [DataMember]
        public List<string> Tags { get; set; }

        [DataMember]
        public string AspNetSessionId { get; set; }
    }
}

Вы можете написать свой собственный HTTPModule для сжатия GZip выше по течению, но сначала я попытался бы сделать это.

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

Ответ 5

У меня очень приятный опыт работы с ServiceStack:

http://www.servicestack.net.

Это в основном ваш последний вариант; довольно тонкий слой поверх HttpHandlers с быстрой сериализацией XML и JSON, предоставляющий REST API.

Сериализация JSV, которую он также предлагает, составляет примерно половину скорости Protobuf.NET, я полагаю, и планируется поддержка ProtoBuf.

Я точно не знаю, работает ли он на Azure, но я не могу придумать причину, почему бы не просто интегрироваться в любое приложение ASP.NET.

Ответ 6

Вот Тесты для разных вариантов сериализации .NET

Из всех сериализаторов JSON я сравнивал свой сериализатор ServiceStack Json, который работает лучше всего на 3 раза быстрее, чем JSON.NET. Вот пара внешних тестов, показывающих это:

ServiceStack (альтернативный вариант с открытым исходным кодом для WCF) предварительно настроен с .NET быстрее JSV и JSON Текстовые сериализаторы OOB.

Я вижу, что кто-то включает в себя длительную настройку того, как вы можете сгибать WCF, чтобы настроить его на использование более медленного JSON Serializer, поставляемого с .NET. В Service Stack каждый веб-сервис автоматически доступен через JSON, XML, SOAP (вкл. JSV, CSV, HTML) автоматически без какой-либо конфигурации, поэтому вы можете выбрать наиболее подходящую конечную точку без каких-либо дополнительных усилий.

То же количество кода и конфигурации для примера WCF в Service Stack справедливо:

public class PostMessage
{
    public string Text { get; set; }
    public List<string> Tags { get; set; }
    public string AspNetSessionId { get; set; }
}

public class GetUpdatesService : IService<GetUpdates>
{
    public object Execute(GetUpdates request){ ... }
}

public class PostMessageService : IService<PostMessage>
{
    public object Execute(PostMessage request){ ... }
}

Примечание. Украшение ваших DTO с помощью [DataContract] является необязательным.

Пример ServiceStack Hello World показывает все ссылки разных форматов, страницы метаданных XSD Schemas и SOAP WSDL автоматически доступны после создания веб-службы.

Ответ 7

Я обнаружил, что инициализация памяти blob (CreateCloudBlobClient(), GetContainerReference() и т.д.) довольно медленная. Это хорошая идея, чтобы принять это во внимание при проектировании услуг Azure.

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