如何使用.NET在Azure上实现高性能REST API?

时间:2011-09-27 15:34:21

标签: performance api rest azure

我们在 Windows Azure 上托管了一个.NET Web角色,仅提供 REST API ,只有少量Web方法。< / p>

其他云托管应用(非浏览器)非常积极地使用API​​。每种方法都是无状态的,可以直接扩展,并且通常与Blob或表存储交互。

与大多数经典API相反,数据上传到API的数量通常比从API下载的数据大得多 。然后,平均消息的大小通常也很大(即大于100kB)。

到目前为止,我们在带有POX消息的ASP.NET Forms上使用WCF (Plain Old Xml)。前端性能不是很好,罪魁祸首是:

  • XML是详细的==&gt;带宽限制。
  • ASP.NET + WCF + WcfRestContrib缓慢解析/序列化消息==&gt; CPU限制。

我想知道实现最高可能的前端性能的最佳策略是什么,以减少支持工作负载所需的VM数量。

我正在考虑的可能策略:

  • 弃用XML以支持ProtoBuf。
  • 添加上游 GZip压缩(经典HTTP压缩仅应用下游)。
  • 完全放弃WCF以支持原始HttpHandler

是否有人对各种替代方案进行了基准测试,以实现每个Azure VM的大部分用途?

Ps:隐含地引用Lokad Forecasting API,但试图以更一般的方式表达问题。

7 个答案:

答案 0 :(得分:3)

您的XML是否通过反射序列化(即使用属性等)?如果是,则protobuf-net代表much, much faster

事实上,即使使用显式getter和setter Func<>自定义XML序列化,您仍然可以通过protobuf-net看到一些显着的增益。在我们的例子中,根据被序列化对象的大小和内容,我们看到序列化时间增加了5-15%。

使用protobuf-net也会对可用带宽产生影响,但这在很大程度上取决于您的内容。

我们的系统与您的系统听起来完全不同,但我们发现,与其他流程相比,WCF本身具有几乎不可察觉的低开销。像dotTrace这样的探查器可能有助于识别切换到protobufs后可以保存的位置。

答案 1 :(得分:3)

您的服务收到的邮件大小是否太大,因为邮件中有大量数据或者包含文件?

如果是第一种情况,那么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,并简单地将文件写入返回的对象中。与Stream参数一样,WCF将直接将Stream的内容输出到结果体中,而不对其进行任何转换。

答案 2 :(得分:3)

本文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字节,您应该转为唠叨以提高性能(来源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);

    }
}

您可以像这样在.NET 4中序列化为JSON:

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; }
    }
}

您可以为上游GZip压缩编写自己的HTTPModule,但我会先尝试上面的内容。

最后,确保您的表存储与使用它们的服务位于同一位置。

答案 3 :(得分:3)

我使用ServiceStack获得了非常愉快的体验:

http://www.servicestack.net

这基本上是你的最后选择; HttpHandlers之上的一个相当薄的层,具有快速XML和JSON序列化,可以公开REST API。

它提供的JSV序列化速度大约是我认为的Protobuf.NET的一半,并且计划支持ProtoBuf。

我不确定它是否在Azure上运行,但我想不出原因,因为它只是集成到任何ASP.NET应用程序中。

答案 4 :(得分:1)

在您的POC中,我认为您可以在测试某些方案时从等式中删除Azure。

如果这是真正的带宽,压缩肯定是一种选择,但如果这个Web服务将被打开到“公共”而不是简单地由您控制的应用程序使用,则可能会出现问题。在异质环境中尤其如此。

一个不太详细的格式是一个选项,只要你有一个很好的方法可以通过格式错误来RESTful地传递失败。 XML使这很容易。由于缺乏ProtoBuf的经验,它确实在这个领域有一定的安全性,所以如果带宽是你的问题并且可以解决解析问题的速度,它可能是一个非常好的选择。我首先将它POC放在Azure之外,然后将其放入。

如果您有证据证明WCF开销是个问题,我只会运行原始的HttpHandler方向。 Azure非常难以在配置中调试,我不相信添加原始HttpHandler的额外问题是正确的方向。

答案 5 :(得分:1)

以下是Benchmarks for different .NET serialization options

在所有JSON Serializers中,我对我的ServiceStack的Json序列化程序进行了基准测试,其性能比JSON.NET 快3倍。以下是几个外部基准测试:

  1. http://daniel.wertheim.se/2011/02/07/json-net-vs-servicestack/
  2. http://theburningmonk.com/2011/08/performance-test-json-serializers/
  3. ServiceStack(WCF的开源替代品)预先配置了.NET最快的JSVJSON文本序列化器OOB。

    我看到有人在如何弯曲WCF以配置它以使用.NET附带的较慢的JSON Serializer时包含冗长的配置。在Service Stack中,每个Web服务都可以自动通过JSON,XML,SOAP(包括JSV,CSV,HTML)自动获取,无需任何配置,因此您无需任何额外工作即可选择最合适的端点。

    服务堆栈中WCF示例的相同数量的代码和配置只是:

    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){ ... }
    }
    

    注意:使用[DataContract]装饰您的DTO是可选的。

    ServiceStack Hello World示例显示创建Web服务后,所有链接的不同格式,元数据页XSD架构和SOAP WSDL自动可用。

答案 6 :(得分:0)

我发现blob存储(CreateCloudBlobClient(),GetContainerReference()等)的初始化非常慢。在设计Azure服务时考虑这一点是个好主意。

我为需要blob访问的任何内容提供单独的服务,因为它拖延了纯数据库请求的时间。