WCF HttpTransport:stream式传输与缓冲传输模式
我有一个自托pipe的WCF服务(v4框架),通过基于HttpTransport
的自定义绑定公开。 该绑定使用了一个自定义的MessageEncoder
,它几乎是一个BinaryMessageEncoder
,并增加了gzip压缩function。
Silverlight和Windows客户端使用Web服务。
问题 :在某些情况下,服务必须返回非常大的对象,偶尔在响应多个并发请求时(即使任务pipe理器报告进程为〜600 Mb),抛出OutOfMemoryexception。 自定义编码器发生exception时,消息即将被压缩,但我相信这只是一个症状,而不是原因。 例外情况是“未能分配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);
然后我做了一个实验:将TransferMode
从Buffered
更改为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);
神奇的是,没有OutOfMemoryexception了 。 对于小消息来说,该服务稍微慢一点,但随着消息大小的增长,差异会越来越小。 行为(包括速度和OutOfMemoryexception)是可重复的,我对这两种configuration都做了几次testing,结果是一致的。
问题解决了,但是:我不能解释自己在这里发生了什么。 我的惊讶源于我没有以任何方式改变合同的事实。 也就是说,我没有像一个Stream
参数一样创build一个合约,就像通常为stream式传输的消息所做的那样。 我仍然使用具有相同DataContract和DataMember属性的复杂类。 我只是修改了端点 ,就这些。
我以为设置TransferMode只是一个方法来启用适当形成的合同stream,但显然不止这些。 任何人都可以解释一下,当你改变TransferMode
时候实际发生了什么?
当你使用'GZipMessageEncodingBindingElement'时,我假设你正在使用MS GZIP示例。
看看GZipMessageEncoderFactory.cs中的DecompressBuffer()
,你就会明白缓冲模式下发生了什么。
举例来说,假设您有一个未压缩大小为50M,压缩大小为25M的消息。
DecompressBuffer将接收(1) 25M大小的“ArraySegment缓冲区”参数。 然后,该方法将创build一个MemoryStream,使用(2) 50M解压缩缓冲区。 然后它将执行一个MemoryStream.ToArray(),将内存stream缓冲区复制到一个新的(3) 50M大字节数组中。 然后从ATTREAST(4) 50M +的BufferManager中获取另一个字节数组,实际上,它可以更多 – 在我的情况下,对于50M数组,总是67M。
在DecompressBuffer的末尾,(1)将被返回到BufferManager(这似乎永远不会被WCF清除),(2)和(3)受到GC(这是asynchronous的,如果你比GC快,即使清理了足够的内存,也可能会遇到OOMexception)。 (4)大概会被返回给你的BinaryMessageEncodingBindingElement.ReadMessage()中的BufferManager。
综上所述,对于你的50M消息来说,你的缓冲场景将暂时占用25 + 50 + 50 +例如65 = 190M的内存,其中一些需要asynchronousGC,一些由BufferManagerpipe理,最糟糕的情况是它会在内存中保留大量未使用的数组,这些数组在后续请求中不可用(例如太小),也不符合GC的条件。 现在想象你有多个并发请求,在这种情况下,BufferManager将为所有并发请求创build单独的缓冲区,除非你手动调用BufferManager.Clear(),否则永远不会被清理,我不知道有什么办法可以做到这一点与WCF使用的缓冲区pipe理器,请参阅此问题: 如何防止WCF客户端应用程序中的BufferManager / PooledBufferManager浪费内存? ]
更新:迁移到IIS7 Http Compression( wcf条件压缩 ) 内存消耗后,cpu加载和启动时间下降 (没有数字方便),然后从缓冲迁移到streamTransferMode( 我怎样才能防止我WCF中的BufferManager / PooledBufferManager客户端应用程序从浪费内存? ) 我的WCF客户端应用程序的内存消耗已经从630M(峰值)/ 470M(连续)下降到270M(峰值和连续) !
我有一些WCF和stream媒体的经验。
基本上,如果您没有将TransferMode
设置为stream,那么它将默认为缓冲。 所以,如果你发送大量的数据,它将在你的内存中build立数据,然后一旦所有的数据被加载并准备好发送,就发送它。 这就是为什么你的内存错误,因为数据是非常大的,而不是你的机器的内存。
现在,如果您使用stream式传输,那么它会立即开始将数据块发送到另一个端点,而不是将其缓冲起来,从而使内存使用量极小。
但这并不意味着接收器也必须设置为stream式传输。 如果他们没有足够的内存来存储数据,他们可能会被设置为缓冲区,并会遇到与发件人相同的问题。
为了获得最好的结果,应该设置两个端点来处理数据stream(对于大数据文件)。
通常,对于stream式处理,您使用MessageContracts
而不是DataContracts
因为它使您可以更好地控制SOAP结构。
有关详细信息,请参阅MessageContracts和Datacontracts上的这些MSDN文章。 这里有更多关于缓冲vsstream的信息 。
我认为(我可能是错的),限制用户只是一个Stream
参数在使用Streamed
传输模式的操作契约中,来自于WCF将stream数据放入SOAP消息的主体部分并开始将其作为用户开始阅读stream。 所以,我认为他们很难在一个数据stream中复制任意数量的数据stream。 例如,假设您有一个具有3个stream参数的操作契约,并且客户端上的三个不同线程开始从这三个stream中读取。 你怎么能这样做,而不使用一些algorithm和额外的程序来复用这三个不同的数据stream(WCF现在没有)
至于你的其他问题,很难说没有看到你的完整的代码实际上正在进行,但我认为通过使用gzip,你实际上是压缩所有的消息数据到一个字节数组,把它交给WCF和客户端当客户端请求SOAP消息时,底层通道打开一个stream来读取消息和WCF通道进行stream式传输,开始stream式数据,因为它是消息的主体。
无论如何,你应该注意到设置MessageBodyMember
属性只是告诉WCF这个成员应该作为SOAP主体进行stream式传输,但是当你使用自定义的编码器和绑定时,大多数情况下你会select传出消息的样子。
caching:上传/下载前需要将整个文件放入内存中。 这是安全地传输小文件非常有用的方法。
stream式传输:文件可以以块的forms传输。