【问题标题】:How to achieve high-performance REST API on Azure with .NET?如何使用 .NET 在 Azure 上实现高性能 REST API?
【发布时间】:2011-09-27 15:34:21
【问题描述】:

我们在 Windows Azure 上托管了一个 .NET Web 角色,它只提供一个 REST API,只有 少数几个 Web 方法

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

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

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

  • XML 很冗长 ==> 带宽限制。
  • ASP.NET + WCF + WcfRestContrib 解析/序列化消息很慢 ==> CPU 限制。

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

我正在考虑的可能策略:

  • 放弃 XML 以支持 ProtoBuf。
  • 添加上游 GZip 压缩(经典HTTP 压缩仅适用于下游)。
  • 完全放弃 WCF,转而使用原始 HttpHandlers。

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

Ps:隐含提及Lokad Forecasting API,但试图以更笼统的方式表达问题。

【问题讨论】:

  • 您发现什么是问题/解决方案?

标签: performance api rest azure


【解决方案1】:

您的 XML 是否通过反射进行序列化(即使用属性等)?如果是这样,那么protobuf-net 就是much, much faster

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

使用 protobuf-net 也会增加可用带宽,但这在很大程度上取决于您的内容。

我们的系统听起来与您的完全不同,但 FWIW 我们发现 WCF 本身的开销与流程的其余部分相比几乎无法察觉。像 dotTrace 这样的分析器可能有助于确定在切换到 protobufs 后可以保存的位置。

【讨论】:

【解决方案2】:

您的服务收到的消息这么大,是因为消息中有大量数据还是因为它们包含文件?

如果是第一种情况,那么 ProtoBuf 确实看起来是一个非常不错的选择。

如果消息很大是因为它嵌入了文件,那么我一直在成功使用的一种策略是为您的服务方法创建两种不同的架构:一种用于上传和下载文件的方法,另一种用于仅发送和接收消息。

文件相关方法将简单地以二进制形式传输 HTTP 请求正文中的文件,无需任何转换或编码。其余参数将使用请求 URL 发送。

对于文件上传,在 WCF REST 服务中,您必须在服务方法中声明代表流类型文件的参数。例如:

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

当遇到 Stream 参数时,WCF 将直接从请求正文中获取其内容,而不对其进行任何处理。一个服务方法只能有一个 Stream 类型的参数(这是有道理的,因为每个 HTTP 请求只有一个主体)。

上述方法的缺点是除了代表文件的参数外,所有其他参数都需要是基本类型(如字符串、数字、GUID)。你不能传递任何复杂的对象。如果您需要这样做,则必须为其创建一个单独的方法,因此您最终可能会拥有两种方法(在运行时将转换为两次调用),而目前您只有一个。但是,直接在请求正文中上传文件应该比序列化文件更有效,所以即使有额外的调用,事情也应该得到改进。

为了从服务下载文件,您需要将 WCF 方法声明为返回 Stream,并简单地将文件写入返回的对象中。与 Stream 参数一样,WCF 会将 Stream 的内容直接输出到结果主体中,而不对其进行任何转换。

【讨论】:

    【解决方案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 字节,您应该转而使用 nagling 来提高性能(来源 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,但我会先尝试上面的内容。

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

    【讨论】:

    【解决方案4】:

    我对 ServiceStack 有过非常愉快的体验:

    http://www.servicestack.net

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

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

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

    【讨论】:

      【解决方案5】:

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

      如果这是真正的带宽,那么压缩当然是一种选择,但如果此 Web 服务将向“公众”开放而不是仅由您控制的应用程序使用,则可能会出现问题。在异构环境中尤其如此。

      一个不太冗长的格式是一种选择,只要您有一个好的方法来通过 RESTful 方式传达由于格式错误而导致的故障。 XML 使这非常容易。由于缺乏 ProtoBuf 的经验,它在这方面似乎有一定的安全性,所以如果带宽是您的问题并且可以解决解析速度问题,它可能是一个非常好的选择。我会先在 Azure 之外对其进行 POC,然后再将其放入。

      如果您有证据表明 WCF 开销是一个问题,我只会运行原始 HttpHandler 方向。 Azure 很难在配置中进行调试,因此我不相信添加原始 HttpHandlers 的附加问题是正确的方向。

      【讨论】:

        【解决方案6】:

        这里是Benchmarks for different .NET serialization options

        在所有 JSON 序列化程序中,我对 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/

        ServiceStack(WCF 的开源替代品)预配置了 .NET 最快的 JSVJSON 文本序列化程序 OOB。

        我看到有人包含关于如何弯曲 WCF 以将其配置为使用 .NET 附带的较慢 JSON 序列化程序的冗长配置。在Service Stack 中,每个 Web 服务都可以通过 JSON、XML、SOAP(包括 JSV、CSV、HTML)自动获得,无需任何配置,因此您无需任何额外努力即可选择最合适的端点。

        Service Stack 中的 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 示例显示了所有不同格式的链接、元数据页面 XSD 架构和 SOAP WSDL 在您创建 Web 服务后自动可用。

        【讨论】:

          【解决方案7】:

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

          对于任何需要 blob 访问的东西,我都有单独的服务,因为它拖长了纯 db 请求的时间。

          【讨论】:

            猜你喜欢
            • 2015-11-30
            • 2019-10-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多