2014-10-12 52 views
3

我们有一个WCF方法返回一个流 - 通过REST公开。 我们比较(从网站)定期下载到WCF方法,我们发现了70MB文件如下:REST WCF - 流下载速度非常慢,无法更改65535(64KB)块

    在正规的网站
  • - 下载耗时到10秒 - 1MB块大小
  • 在WCF的方法 - 花了约20秒 - 块大小总是65535字节

我们有一个实际流入另一个产品的自定义流,这会使时代差异变得更糟 - 常规站点需要1分钟,而WCF需要2分钟。

因为我们需要支持非常大的文件 - 它变得越来越重要。

我们在调试时停下来,发现WCF调用的流的“读取”方法始终有一个块大小为65,535 - ,这导致缓慢

我们尝试了多个服务器配置 - 是这样的:

端点:

<endpoint address="Download" binding="webHttpBinding" bindingConfiguration="webDownloadHttpBindingConfig" behaviorConfiguration="web" contract="IAPI" /> 

结合:

<binding name="webDownloadHttpBindingConfig" maxReceivedMessageSize="20000000" maxBufferSize="20000000" transferMode="Streamed"> 
           <readerQuotas maxDepth="32" maxStringContentLength="20000000" maxArrayLength="20000000" maxBytesPerRead="20000000" maxNameTableCharCount="20000000"/> 
           <security mode="Transport"> 
            <transport clientCredentialType="None" proxyCredentialType="None" realm=""/> 
           </security> 
         </binding> 

的客户端,它是一个REST客户端(不能使用WCF绑定 - 我们不想参考它) - 是这样构建的:

System.Net.HttpWebRequest request = (HttpWebRequest)WebRequest.Create(CombineURI(BaseURL, i_RelativeURL)); 

    request.Proxy = null; // We are not using proxy 
    request.Timeout = i_Timeout; 
    request.Method = i_MethodType; 
    request.ContentType = i_ContentType; 

    string actualResult = string.Empty; 
    TResult result = default(TResult); 
    if (!string.IsNullOrEmpty(m_AuthenticationToken)) 
    { 
     request.Headers.Add(ControllerConsts.AUTH_HEADER_KEY, m_AuthenticationToken); 
    } 

    using (var response = request.GetResponse()) 
     { 
      using (Stream responseStream = response.GetResponseStream()) 
      { 
       byte[] buffer = new byte[1048576]; 

       int read; 
       while ((read = responseStream.Read(buffer, 0, buffer.Length)) > 0) 
       { 
       o_Stream.Write(buffer, 0, read); 
       } 
      } 
     } 

基本上我们只是流入一个流。

所以,无论我们做 - 服务器始终接收的65,535块大小(我们尝试了几种客户端/服务器配置)

我们有什么缺失?

谢谢!

==编辑15年8月4日微软响应== 嗨,我们与微软有关这个案子的工作,这是他们的答案:

当WCF客户端调用返回流的WCF方法,实际上获得对MessageBodyStream实例的引用。 MessageBodyStream最终依赖于WebResponseInputStream实际读出的数据,通过该曲线图的关系:

  • MessageBodyStream有一个成员,消息,引用了InternalByteStreamMessage实例
  • InternalByteStreamMessage有一个成员,bodyWriter,引用一个StreamBasedStreamedBodyWriter实例
  • StreamBasedStreamedBodyWriter有一个引用MaxMessageSizeStream实例的成员流
  • MaxMessageSizeStream有一个引用WebResponseInputStream实例的成员流

当你在流调用Read(),WebResponseInputStream.Read()最终调用(你可以在Visual Studio中设置断点测试这个自己 - 一个警告:在Visual Studio中“仅我的代码”选项 - 必须禁用调试,才能命中断点)。 WebResponseInputStream.Read()的相关部分是以下各项:

    return BaseStream.Read(buffer, offset, Math.Min(count, maxSocketRead)); 

其中maxSocketRead被定义为64KB。 上面的注释maxSocketRead说:“为了避免吹内核缓冲区,我们扼杀了我们的读取。 http.sys处理这个问题,但是System.Net不会做任何这样的限制。“ 这意味着如果指定的读取值太大,则会超出内核本身的缓冲区大小,并导致性能较差,因为它需要执行更多工作。

这是否会导致性能瓶颈?不,它不应该。一次读取太少的字节(比如256字节)会导致性能下降。但64KB应该是一个可以提高性能的值。在这些情况下,真正的瓶颈通常是网络带宽,而不是客户端读取数据的速度。 为了最大限度地提高性能,重要的是读取循环尽可能紧凑(换句话说,读取之间没有明显的延迟)。 我们还要记住,大于80KB的对象将转到.Net中的大对象堆,它的存储管理效率低于“正常”堆(压缩不会在正常情况下发生,因此可能会发生内存碎片) 。

+0

检查[maxRequestLength的。 (http://msdn.microsoft.com/en-us/library/system.web.configuration.httpruntime section.maxrequestlength(v = vs.110).aspx)属性 – 2014-10-13 07:27:24

+0

@LukasKubis - 这不是最大的请求大小吗? (默认是4MB - 不是64KB,因为我们看到块的大小) – ArielB 2014-10-13 13:45:11

+0

http://stackoverflow.com/questions/451376/file-download-through-wcf-slower-than-through-iis – Nahum 2015-02-04 15:00:48

回答

1

我们与微软合作对这种情况下,这是他们的答案:

当WCF客户端调用返回流的WCF方法,它实际上到达一个MessageBodyStream实例的引用。 MessageBodyStream最终依赖于WebResponseInputStream实际读出的数据,通过该曲线图的关系:

MessageBodyStream有一个成员,消息,引用了InternalByteStreamMessage实例 InternalByteStreamMessage有一个成员,bodyWriter,引用一个StreamBasedStreamedBodyWriter实例 StreamBasedStreamedBodyWriter有一个成员,流引用MaxMessageSizeStream实例 MaxMessageSizeStream有一个引用WebResponseInputStream实例的成员流 当您在流上调用Read()时,最终调用WebResponseInputStream.Read()(您可以通过设置断点在Visual Studio中 - 一个警告:Visual Studio中的“Just My Code”选项 - 调试必须被禁用,才能触发断点)。 WebResponseInputStream.Read()的相关部分是以下各项:

   return BaseStream.Read(buffer, offset, Math.Min(count, maxSocketRead)); 

其中maxSocketRead被定义为64KB。上面的注释maxSocketRead说:“为了避免吹内核缓冲区,我们扼杀了我们的读取。 http.sys处理这个问题,但是System.Net不会做任何这样的限制。“这意味着如果指定的读取值太大,则会超出内核本身的缓冲区大小,并导致性能较差,因为它需要做更多的工作。

这是否会导致性能瓶颈?不,它不应该。一次读取太少的字节(比如256字节)会导致性能下降。但64KB应该是一个可以提高性能的值。在这些情况下,真正的瓶颈通常是网络带宽,而不是客户端读取数据的速度。为了最大限度地提高性能,重要的是读取循环尽可能紧凑(换句话说,读取之间没有明显的延迟)。我们还要记住,大于80KB的对象会转到.Net中的大对象堆,与“正常”堆相比,它的内存管理效率较低(在正常情况下不会发生压缩,因此可能会发生内存碎片)。

可能的解决办法:在内存更大的块缓存(例如,使用的MemoryStream和而WCF流调用自定义的“读” - 高速缓存内1MB或更多/更少 - 任何你想要的

。然后,当1MB(或其他值)超过 - 其推送到你的实际的自定义数据流,并继续缓存更大的块

这不是检查,但我认为它应该解决性能问题