简体   繁体   English

WCF HttpTransport:流式传输与缓冲的TransferMode

[英]WCF HttpTransport: streamed vs buffered TransferMode

I have a self-hosted WCF service (v4 framework) that is exposed through a HttpTransport -based custom binding. 我有一个自托管的WCF服务(v4框架),它通过基于HttpTransport的自定义绑定公开。 The binding uses a custom MessageEncoder that is pretty much a BinaryMessageEncoder with the addition of gzip compression functionality. 绑定使用自定义MessageEncoder ,它几乎是BinaryMessageEncoder ,并添加了gzip压缩功能。

A Silverlight and a Windows client consume the web service. Silverlight和Windows客户端使用Web服务。

Problem : in some cases the service had to return very large objects and occasionally threw OutOfMemory exceptions when responding to several concurrent requests (even if Task Manager reported ~600 Mb for the process). 问题 :在某些情况下,服务必须返回非常大的对象,并且在响应多个并发请求时偶尔会抛出OutOfMemory异常(即使任务管理器报告该过程约为600 Mb)。 The exception happened in the custom encoder, when the message was about to be compressed, but I believe this was just a symptom and not the cause. 当消息即将被压缩时,自定义编码器中发生异常,但我认为这只是一种症状,而不是原因。 The exception stated "failed to allocate x Mb" where x was 16, 32 or 64, not a overly huge amount -for this reason I believe something else already put the process near some limit before that. 例外情况是“未能分配x Mb”,其中x为16,32或64,而不是一个过大的数量 - 因此我相信其他事情已经使这个过程接近某个限制。

The service endpoint is defined as follows: 服务端点定义如下:

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

Then I did an experiment: I changed TransferMode from Buffered to StreamedResponse (and modified the client accordingly). 然后我做了一个实验:我将TransferModeBuffered更改为StreamedResponse (并相应地修改了客户端)。 This is the new service definition: 这是新的服务定义:

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);

Magically, no OutOfMemory exceptions anymore . 神奇的是,没有OutOfMemory异常了 The service is a bit slower for small messages, but the difference gets smaller and smaller as message size grows. 对于小消息,服务有点慢,但随着消息大小的增加,差异变得越来越小。 The behavior (both for speed and OutOfMemory exceptions) is reproducible, I did several tests with both configurations and these results are consistent. 行为(速度和OutOfMemory异常)都是可重现的,我用两种配置做了几次测试,这些结果是一致的。

Problem solved, BUT: I cannot explain myself what is happening here. 问题解决了,但是:我无法解释自己这里发生了什么。 My surprise stems from the fact that I did not change the contract in any way . 令我惊讶的是, 我没有以任何方式改变合同 Ie I did not create a contract with a single Stream parameter, etc., as you usually do for streamed messages. 即我没有像单个Stream参数等那样创建合同,就像通常对流式消息一样。 I am still using my complex classes with the same DataContract and DataMember attribute. 我仍在使用具有相同DataContract和DataMember属性的复杂类。 I just modified the endpoint , that's all. 我只修改了端点 ,就是这样。

I thought that setting TransferMode was just a way to enable streaming for properly formed contracts, but obviously there is more than that. 我认为设置TransferMode只是一种为正确形成的合同启用流式传输的方法,但显然还有更多。 Can anybody explain what actually happens under the hood when you change TransferMode ? 当你改变TransferMode时,有人可以解释一下实际发生的事情吗?

As you use 'GZipMessageEncodingBindingElement', I assume you are using the MS GZIP sample. 当您使用'GZipMessageEncodingBindingElement'时,我假设您正在使用MS GZIP示例。

Have a look at DecompressBuffer() in GZipMessageEncoderFactory.cs and you will understand what's going on in buffered mode. 看看GZipMessageEncoderFactory.cs中的DecompressBuffer() ,你就会明白缓冲模式下发生了什么。

For the sake of example, let's say you have a message of uncompressed size 50M, compressed size 25M. 例如,假设您有一条未压缩大小为50M,压缩大小为25M的消息。

DecompressBuffer will receive an 'ArraySegment buffer' param of (1) 25M size. DecompressBuffer将收到(1) 25M大小的'ArraySegment buffer'参数。 The method will then create a MemoryStream, uncompress the buffer into it, using (2) 50M . 然后,该方法将创建一个MemoryStream,使用(2) 50M将缓冲区解压缩到其中。 Then it will do a MemoryStream.ToArray(), copying the memory stream buffer into a new (3) 50M big byte array. 然后它将执行一个MemoryStream.ToArray(),将内存流缓冲区复制到一个新的(3) 50M大字节数组中。 Then it takes another byte array from the BufferManager of AT LEAST (4) 50M+ , in reality, it can be a lot more - in my case it was always 67M for a 50M array. 然后它需要来自AT LEAST(4) 50M +的BufferManager的另一个字节数组,实际上它可以更多 - 在我的情况下,50M阵列总是67M。

At the end of DecompressBuffer, (1) will be returned to the BufferManager (which seems to never get cleared by WCF), (2) and (3) are subject to GC (which is async, and if you are faster than the GC, you might get OOM exceptions even though there would be enough mem if cleaned up). 在DecompressBuffer结束时,(1)将返回到BufferManager(它似乎永远不会被WCF清除),(2)和(3)受GC(这是异步,如果你比GC更快) ,你可能会得到OOM异常,即使有足够的内存,如果清理的话)。 (4) will presumably be given back to the BufferManager in your BinaryMessageEncodingBindingElement.ReadMessage(). (4)可能会被返回到BinaryMessageEncodingBindingElement.ReadMessage()中的BufferManager。

To sum up, for your 50M message, your buffered scenario will temporarily take up 25 + 50 + 50 + eg 65 = 190M memory, some of it subject to asynchronous GC, some of it managed by the BufferManager, which - worst case - means it keeps lots of unused arrays in memory that are neither usable in a subsequent request (eg too small) nor eligible for GC. 总而言之,对于您的50M消息,您的缓冲方案将暂时占用25 + 50 + 50 +例如65 = 190M内存,其中一些受异步GC的影响,其中一些由BufferManager管理,最坏的情况是 - 意味着它在内存中保留了大量未使用的数组,这些数组既不能用于后续请求(例如太小),也不适用于GC。 Now imagine you have multiple concurrent requests, in that case BufferManager will create separate buffers for all concurrent requests, which will never be cleaned up, unless you manually call BufferManager.Clear(), and I don't know of a way to do that with the buffer managers used by WCF, see also this question: How can I prevent BufferManager / PooledBufferManager in my WCF client app from wasting memory? 现在想象你有多个并发请求,在这种情况下,BufferManager将为所有并发请求创建单独的缓冲区, 永远不会被清理,除非你手动调用BufferManager.Clear(),我不知道如何做到这一点使用WCF使用的缓冲区管理器,另请参阅此问题: 如何防止WCF客户端应用程序中的BufferManager / PooledBufferManager浪费内存? ] ]

Update: After migrating to IIS7 Http Compression ( wcf conditional compression ) memory consumption, cpu load and startup time dropped (don't have the numbers handy) and then migrating from buffered to streamed TransferMode ( How can I prevent BufferManager / PooledBufferManager in my WCF client app from wasting memory? ) memory consumption of my WCF client app has dropped from 630M (peak) / 470M (continuous) to 270M (both peak and continuous) ! 更新:迁移到IIS7后,Http压缩( wcf条件压缩内存消耗,cpu加载和启动时间丢失 (没有方便的数字),然后从缓冲转移到流式TransferMode( 如何防止我的WCF中的BufferManager / PooledBufferManager客户端应用程序浪费内存?我的WCF客户端应用程序的内存消耗从630M(峰值)/ 470M(连续)下降到270M(峰值和连续)

I've had some experience with WCF and streaming. 我有一些WCF和流媒体的经验。

Basically, if you don't set the TransferMode to streamed, then it'll default to buffered. 基本上,如果您没有将TransferMode设置为流式传输,那么它将默认为缓冲。 So if you are sending large pieces of data, it's going to build up the data on your end in memory and then send it once all the data is loaded and ready to be sent. 因此,如果您要发送大量数据,它将在内存中构建数据,然后在加载所有数据并准备发送后发送。 This is why you were getting out of memory errors because the data was very large and more than your machine's memory. 这就是为什么你出现内存不足的原因,因为数据非常庞大而且超出了你机器的内存。

Now if you use streamed, then it'll immediately start sending chunks of data to the other endpoint instead of buffering it up, making memory usage very minimal. 现在,如果您使用流式传输,那么它将立即开始将数据块发送到另一个端点而不是缓冲它,从而使内存使用非常少。

But this doesn't mean that the receiver also has to be set up for streaming. 但这并不意味着接收器也必须设置为流式传输。 They could be setup to buffer and will experience the same problem as the sender did if they do not have sufficient memory for your data. 它们可以设置为缓冲区,并且如果它们没有足够的内存用于数据,则会遇到与发送方相同的问题。

For the best results, both endpoints should be setup to handle streaming (for large data files). 为获得最佳结果,应设置两个端点以处理流(对于大型数据文件)。

Typically, for streaming, you use MessageContracts instead of DataContracts because it gives you more control over the SOAP structure. 通常,对于流式传输,您使用MessageContracts而不是DataContracts因为它使您可以更好地控制SOAP结构。

See these MSDN articles on MessageContracts and Datacontracts for more info. 有关详细信息,请参阅有关MessageContractsDatacontracts的这些MSDN文章。 And here is more info about Buffered vs Streamed . 这里有更多关于Buffered vs Streamed的信息

I think (and I might be wrong) that, restricting users to just a Stream parameter in operation contracts which use Streamed transfer mode, comes from the fact that WCF puts stream data in the body section of SOAP message and starts to transfer it as the user starts reading the stream. 我认为(我可能错了)将用户限制为仅使用Streamed传输模式的操作合同中的Stream参数来自WCF将流数据放入SOAP消息的主体部分并开始将其作为传输进行传输的事实。用户开始阅读流。 So, I think it would have been difficult for them to multiplex arbitrary number of streams in a single data flow. 因此,我认为他们很难在单个数据流中复用任意数量的流。 eg, suppose you have an operation contract with 3 stream parameters and three different threads on the client start to read from these three streams. 例如,假设您有一个具有3个流参数的操作合同,并且客户端上的三个不同线程开始从这三个流中读取。 How could you do that without using some algorithm and extra programming to multiplex these three different data flows (which WCF lacks right now) 如果不使用一些算法和额外的编程来复用这三种不同的数据流(WCF目前缺乏),你怎么能这样做呢?

As for your other question, it's hard to tell what is actually going on without seeing your complete code, but I think by using gzip, you are actually compressing all the message data into a byte array, handing it over to WCF and on the client side, when client asks for the SOAP message, the underlying channel opens a stream to read message and WCF channel for streamed transfer, starts streaming data as it was the Body of the message. 至于你的另一个问题,很难在没有看到完整代码的情况下告诉实际情况,但我认为通过使用gzip,你实际上是将所有消息数据压缩成一个字节数组,将其交给WCF和客户端另外,当客户端请求SOAP消息时,底层通道打开一个流来读取消息,WCF通道进行流式传输,启动流数据,因为它是消息的主体。

Anyway, you should note that setting MessageBodyMember attribute just tells WCF that this member should be streamed as the SOAP body, but when you use custom encoder and binding, it is mostly your choice what the outgoing message will look like. 无论如何,您应该注意,设置MessageBodyMember属性只是告诉WCF该成员应该作为SOAP主体进行流式处理,但是当您使用自定义编码器和绑定时,主要是您选择外发消息的外观。

Buffered: it needs to put the entire file in memory before uploading/downloading. 缓冲:在上传/下载之前需要将整个文件放入内存中。 this is approach is very useful for transferring small files, securely. 这种方法对于安全地传输小文件非常有用。

Streamed: the file can be transferred in the form of chunks. 流式传输文件可以以块的形式传输。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 从wcf绑定transferMode从“Buffered”更改为“Streamed”是否被认为是客户端的重大变化? - Is changing from wcf binding transferMode from “Buffered” to “Streamed” considered a breaking change for the client? WCF NetTcpBinding缓冲与流式处理性能问题 - WCF NetTcpBinding Buffered vs Streamed performance problems 如何将自定义对象序列化为流(WCF HttpBinding-TransferMode =流式) - How to seralize custom objects into stream (WCF HttpBinding - TransferMode = Streamed) 使用basichttpBinding和流的transfermode的WCF中出现错误请求 - error Bad Request in WCF using basichttpBinding with transfermode of streamed TransferMode = Streamed时发生TimeoutException - TimeoutException when TransferMode=Streamed NetTcpBinding()与TransferMode.Streamed - NetTcpBinding() with TransferMode.Streamed 将wcf传输程序从缓冲更改为流式传输 - Change a wcf transfer program from buffered to streamed WCF:通过netTcpBinding通过TransferMode = Streamed将文件传输到服务器时,对客户端的响应延迟 - WCF: response to client delayed when transferring file to server with TransferMode=Streamed over netTcpBinding 具有TransferMode = Streamed的WCF REST客户端-记录整个(“原始”)http请求/响应 - WCF REST client with TransferMode=Streamed - logging entire (“raw”) http requests/responses WCF服务可以同时支持缓冲和流传输模式吗? - Can a WCF service support both Buffered and Streamed transfer modes?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM