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

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

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

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

Пока что мы используем WCF поверх форм ASP.NET с сообщениями POX (Обычный)Старый XML).Производительность внешнего интерфейса не очень хорошая, виновники:

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

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

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

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

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

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

Ответы [ 7 ]

3 голосов
/ 02 октября 2011

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

http://www.servicestack.net.

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

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

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

3 голосов
/ 02 октября 2011

В этой статье 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-множественные потоки -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 байт, вам следует повернуться, чтобы повысить производительность (источник 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>  

Определите ваши API-интерфейсы JSON примерно так:

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 в восходящем направлении, но я бы сначала попробовал вышеописанное.

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

3 голосов
/ 29 сентября 2011

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

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

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

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

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

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

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

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

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

3 голосов
/ 27 сентября 2011

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

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

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

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

1 голос
/ 03 октября 2011

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

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

  1. http://daniel.wertheim.se/2011/02/07/json-net-vs-servicestack/
  2. http://theburningmonk.com/2011/08/performance-test-json-serializers/

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 и WSDL SOAP, автоматически доступные после создания веб-службы.

1 голос
/ 27 сентября 2011

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

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

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

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

0 голосов
/ 29 сентября 2011

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

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

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