REST WCF - 流下载非常慢,有 65535 (64KB) 块无法更改

本文关键字:64KB 65535 WCF 下载 非常 REST | 更新日期: 2023-09-27 18:26:14

我们有一个WCF方法,该方法返回一个流 - 通过REST公开。我们将常规下载(从网站(与WCF方法进行了比较,我们发现70MB文件具有以下结果:

  • 在常规站点中 - 下载需要~10秒 - 1MB块大小
  • 在 WCF 方法中 - 耗时 ~20 秒 - 块大小始终为 65,535 字节

我们有一个自定义流,它实际上流式传输到另一个产品中,这使得时间差异变得更糟 - 常规站点需要 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 的块大小(我们尝试了几种客户端/服务器配置(

我们缺少什么?

谢谢!

== 编辑 8/4/15 Microsoft响应 ==嗨,我们与微软就此案进行了合作,这是他们的答案:

当 WCF 客户端调用返回 Stream 的 WCF 方法时,它实际上获取对 MessageBodyStream 实例的引用。MessageBodyStream 最终依赖于 WebResponseInputStream 通过以下关系图实际读取数据:

  • MessageBodyStream 有一个成员 message,它引用 InternalByteStreamMessage 实例
  • InternalByteStreamMessage 有一个成员 bodyWriter,它引用了一个 StreamBasedStreamedBodyWriter 实例
  • StreamBasedStreamedBodyWriter 有一个成员 stream,它引用 MaxMessageSizeStream 实例
  • MaxMessageSizeStream 有一个成员 stream,它引用 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 中的大型对象堆,该堆的内存管理效率低于"正常"堆(在正常情况下不会进行压缩,因此可能会发生内存碎片(。

REST WCF - 流下载非常慢,有 65535 (64KB) 块无法更改

我们与微软合作处理此案,这是他们的答案:

当 WCF 客户端调用返回 Stream 的 WCF 方法时,它实际上获取对 MessageBodyStream 实例的引用。MessageBodyStream 最终依赖于 WebResponseInputStream 通过以下关系图实际读取数据:

MessageBodyStream 有一个成员 message,它引用 InternalByteStreamMessage 实例InternalByteStreamMessage 有一个成员 bodyWriter,它引用了一个 StreamBasedStreamedBodyWriter 实例StreamBasedStreamedBodyWriter 有一个成员 stream,它引用 MaxMessageSizeStream 实例MaxMessageSizeStream 有一个成员 stream,它引用 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(或其他值(超过 - 将其推送到实际的自定义流中,并继续缓存更大的块

这没有被检查,但我认为它应该解决性能问题。