【发布时间】:2014-12-07 01:21:28
【问题描述】:
我们有一个返回流的 WCF 方法 - 通过 REST 公开。 我们将常规下载(从网站)与 WCF 方法进行了比较,我们发现 70MB 文件如下:
- 在常规站点中 - 下载大约需要 10 秒 - 1MB 块大小
- 在 WCF 方法中 - 耗时约 20 秒 - 块大小始终为 65,535 字节。
我们有一个自定义流实际上会流入另一个产品,这使得时间差变得更糟 - 常规站点需要 1 分钟,而 WCF 需要 2 分钟。
因为我们需要支持非常大的文件 - 这变得至关重要。
我们停止调试,发现 WCF 调用的 Stream 的方法“Read”始终具有 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 微软回复 == 嗨,我们与微软就这个案例进行了合作,这是他们的回答:
当 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 中的“仅我的代码”选项 - 必须调试被禁用,以便命中断点)。 WebResponseInputStream.Read() 的相关部分如下:
return BaseStream.Read(buffer, offset, Math.Min(count, maxSocketRead));
其中 maxSocketRead 定义为 64KB。 maxSocketRead 上面的评论说“为了避免破坏内核缓冲区,我们限制了我们的读取。 http.sys 可以很好地处理这个问题,但 System.Net 不做任何这样的限制。”。 这意味着如果您指定的读取值太大,则会超出内核自己的缓冲区大小,并导致性能下降,因为它需要做更多的工作。
这会导致性能瓶颈吗?不,不应该。一次读取太少的字节(例如 256 字节)会导致性能下降。但是 64KB 应该是一个可以带来良好性能的值。在这些情况下,真正的瓶颈通常是网络带宽,而不是客户端读取数据的速度。 为了最大限度地提高性能,读取循环尽可能紧凑是很重要的(换句话说,读取之间没有明显的延迟)。 我们还要记住,大于 80KB 的对象会进入 .Net 中的大对象堆,它的内存管理效率低于“正常”堆(在正常情况下不会发生压缩,因此可能会发生内存碎片)。
【问题讨论】:
-
检查MaxRequestLength属性
-
@LukasKubis - 这不是最大请求大小吗? (默认为 4MB - 而不是我们看到的块大小的 64KB)
-
@NahumLitvin - 我使用的是 http 绑定而不是 TCP。 http有解决方案吗?
-
嗨@NahumLitvin,请查看我的编辑 - 微软回复了,看来这无法更改。我们将检查另一个方向 - 我们将在内存中缓存 1MB 的读取,然后才写入我们的自定义流 - 我们将检查这种方法是否会提高性能。 (意味着到其他产品的流将是 1MB 块)