WCF HttpTransport:流式传输与缓冲传输模式

Posted

技术标签:

【中文标题】WCF HttpTransport:流式传输与缓冲传输模式【英文标题】:WCF HttpTransport: streamed vs buffered TransferMode 【发布时间】:2011-05-01 21:59:51 【问题描述】:

我有一个通过基于HttpTransport 的自定义绑定公开的自托管WCF 服务(v4 框架)。绑定使用自定义的MessageEncoder,它几乎是BinaryMessageEncoder,并添加了 gzip 压缩功能。

Silverlight 和 Windows 客户端使用 Web 服务。

问题:在某些情况下,服务必须返回非常大的对象,并且在响应多个并发请求时偶尔会抛出 OutOfMemory 异常(即使任务管理器报告该进程约为 600 Mb)。异常发生在自定义编码器中,当消息即将被压缩时,但我相信这只是一个症状而不是原因。异常声明“未能分配 x Mb”,其中 x 为 16、32 或 64,而不是一个过大的数量 - 因此,我相信在此之前其他一些东西已经使该过程接近某个限制。

服务端点定义如下:

var transport = new HttpTransportBindingElement(); // quotas omitted for simplicity
var binaryEncoder = new BinaryMessageEncodingBindingElement(); // Readerquotas omitted for simplicity
var customBinding = new CustomBinding(new GZipMessageEncodingBindingElement(binaryEncoder), transport);

然后我做了一个实验:我将TransferModeBuffered更改为StreamedResponse(并相应地修改了客户端)。这是新的服务定义:

var transport = new HttpTransportBindingElement()

    TransferMode = TransferMode.StreamedResponse // <-- this is the only change
;
var binaryEncoder = new BinaryMessageEncodingBindingElement(); // Readerquotas omitted for simplicity
var customBinding = new CustomBinding(new GZipMessageEncodingBindingElement(binaryEncoder), transport);

神奇的是,不再有 OutOfMemory 异常。对于小消息,该服务有点慢,但随着消息大小的增长,差异会越来越小。 行为(速度和 OutOfMemory 异常)是可重现的,我对这两种配置进行了几次测试,这些结果是一致的。

问题解决了,但是:我无法解释自己这里发生了什么。令我惊讶的是我没有以任何方式更改合同。 IE。我没有像您通常对流式消息那样使用单个 Stream 参数等创建合同。我仍在使用具有相同 DataContract 和 DataMember 属性的复杂类。 我只是修改了端点,仅此而已。

我认为设置 TransferMode 只是为正确形成的合同启用流式传输的一种方式,但显然不止于此。 任何人都可以解释当您更改 TransferMode 时实际发生了什么?

【问题讨论】:

附言。我知道这个问题***.com/questions/2312408 很相似,但没有得到答案(也许有更多细节会更容易) 【参考方案1】:

当您使用“GZipMessageEncodingBindingElement”时,我假设您使用的是 MS GZIP 示例。

看看 GZipMessageEncoderFactory.cs 中的DecompressBuffer(),你就会明白缓冲模式下发生了什么。

举个例子,假设您有一条未压缩大小为 50M,压缩大小为 25M 的消息。

DecompressBuffer 将接收 (1) 25M 大小的“ArraySegment 缓冲区”参数。然后该方法将创建一个 MemoryStream,将缓冲区解压缩到其中,使用 (2) 50M。然后它将执行 MemoryStream.ToArray(),将内存流缓冲区复制到一个新的 (3) 50M 大字节数组中。然后它从 AT LEAST (4) 50M+ 的 BufferManager 中获取另一个字节数组,实际上,它可以更多——在我的例子中,50M 数组总是 67M。

在 DecompressBuffer 结束时,(1) 将被返回到 BufferManager(这似乎永远不会被 WCF 清除),(2) 和 (3) 受到 GC(这是异步的,如果你更快与 GC 相比,即使清理后会有足够的内存,您也可能会遇到 OOM 异常)。 (4) 大概会在您的 BinaryMessageEncodingBindingElement.ReadMessage() 中返回给 BufferManager。

总而言之,对于您的 50M 消息,您的缓冲场景将暂时占用 25 + 50 + 50 + 例如65 = 190M 内存,其中一些受到异步 GC,其中一些由 BufferManager 管理,这 - 最坏的情况 - 意味着它在内存中保留了许多在后续请求中都不可用的未使用数组(例如small) 也没有资格获得 GC。现在假设您有多个并发请求,在这种情况下,BufferManager 将为所有并发请求创建单独的缓冲区,这些缓冲区永远不会被清理,除非您手动调用 BufferManager.Clear(),而我没有知道使用 WCF 使用的缓冲区管理器的方法,另请参阅此问题:How can I prevent BufferManager / PooledBufferManager in my WCF client app from wasting memory?]

更新:迁移到 IIS7 Http Compression (wcf conditional compression) 内存消耗、cpu 负载和启动时间下降(手头没有数字)然后从缓冲迁移到流传输模式 (How can I prevent BufferManager / PooledBufferManager in my WCF client app from wasting memory?) 我的 WCF 客户端应用程序的内存消耗从 630M(峰值)/470M(连续)下降到 270M(峰值和连续)

【讨论】:

今天,2017 年(6 年后),将“BinaryMessageEncodingBindingElement”与 CompressionFormat.GZip 一起使用会更好吗?还有一些其他的实现,也许是原生的,我们可以使用?我仍然遇到一些性能问题,但尚未解决。【参考方案2】:

我对 WCF 和流式传输有一些经验。

基本上,如果您没有将TransferMode 设置为流式传输,那么它将默认为缓冲。因此,如果您要发送大量数据,它将在您的内存中建立数据,然后在所有数据加载并准备好发送后发送。这就是为什么您遇到内存不足错误的原因,因为数据非常大并且超出了您机器的内存。

现在,如果您使用流式传输,那么它会立即开始向另一个端点发送数据块,而不是对其进行缓冲,从而将内存使用量降至最低。

但这并不意味着接收器也必须设置为流式传输。它们可以设置为缓冲,如果它们没有足够的内存来存储您的数据,它们会遇到与发送方相同的问题。

为获得最佳结果,应设置两个端点以处理流式传输(对于大型数据文件)。

通常,对于流式传输,您使用 MessageContracts 而不是 DataContracts,因为它可以让您更好地控制 SOAP 结构。

有关详细信息,请参阅 MessageContracts 和 Datacontracts 上的这些 MSDN 文章。这里有更多关于Buffered vs Streamed的信息。

【讨论】:

感谢您的回答,但它仍然没有加起来:如果我有一个大型复杂对象并且不使用 MessageContract,它仍然必须在被序列化之前作为一个整体(在内存中)发送。这不像我正在读取文件并按字节传输。 我正在阅读您的链接,最后一个链接的最后一句话很有趣:“将传输模式从缓冲更改为流也更改了 TCP 和命名管道传输的本机通道形状(.. .)”。这可能会有所启发。 @Francesco 嗯,我不确定。如果没有 MessageContract,我永远无法让它流式传输文件,内存会爆炸。但是,我没有使用 v4,而是显式使用流,而不是序列化对象。【参考方案3】:

我认为(我可能错了),在使用Streamed 传输模式的操作合同中限制用户仅使用Stream 参数,这是因为 WCF 将流数据放在 SOAP 消息的正文部分并在用户开始阅读流时开始传输它。因此,我认为他们很难在单个数据流中多路复用任意数量的流。例如,假设您有一个带有 3 个流参数的操作合约,并且客户端上的三个不同线程开始从这三个流中读取。如果不使用一些算法和额外的编程来多路复用这三种不同的数据流(WCF 目前缺乏),你怎么能做到这一点

至于您的另一个问题,如果没有看到您的完整代码,很难知道实际发生了什么,但我认为通过使用 gzip,您实际上是将所有消息数据压缩到一个字节数组中,并将其交给 WCF 和在客户端,当客户端请求 SOAP 消息时,底层通道打开一个流来读取消息和 WCF 通道进行流式传输,开始流式传输数据,因为它是消息的正文。

无论如何,您应该注意,设置 MessageBodyMember 属性只是告诉 WCF 该成员应该作为 SOAP 主体进行流式传输,但是当您使用自定义编码器和绑定时,您可以选择传出消息的外观。

【讨论】:

【参考方案4】:

缓冲:它需要在上传/下载之前将整个文件放入内存中。 这种方法对于安全地传输小文件非常有用。

流式传输:文件可以以块的形式传输。

【讨论】:

嗨,欢迎来到 ***。您能否进一步扩展您的答案?它很短,我认为它不能完全回答 OP 的问题。

以上是关于WCF HttpTransport:流式传输与缓冲传输模式的主要内容,如果未能解决你的问题,请参考以下文章

WCF 实时视频流

WCF 流式传输错误(最大邮件大小配额)

wcf 服务错误请求 400 流式传输

WCF流式传输时间

WCF REST 服务流式传输 JSON 对象

流式处理 WCF 大量对象