16

我有一个案例,我需要使用 WCF 使用 wsHttp传输大量序列化对象图(通过NetDataContractSerializer )。我正在使用消息安全,并希望继续这样做。使用此设置,我想传输序列化的对象图,它有时可能接近 300MB 左右,但是当我尝试这样做时,我开始看到 System.InsufficientMemoryException 类型的异常出现。

经过一番研究,默认情况下,在 WCF 中,服务调用的结果似乎包含在默认情况下包含在单个消息中,该消息包含序列化数据,并且默认情况下此数据在服务器上缓冲,直到整个消息被完全写入。因此,由于该缓冲区已满,服务器正在耗尽允许分配的内存资源,从而导致内存异常。我遇到的两个主要建议是使用流式处理或分块来解决这个问题,但是我不清楚这涉及到什么,以及在我当前的设置(wsHttp/NetDataContractSerializer/Message Security)下这两种解决方案是否可行。到目前为止,我知道使用流式消息安全性是行不通的,因为消息加密和解密需要对整个数据集而不是部分消息起作用。然而,分块听起来可能是可能的,但我不清楚如何使用我列出的其他约束来完成。如果有人可以就可用的解决方案以及如何实施它提供一些指导,我将不胜感激。

我应该补充一点,就我而言,我真的不担心与其他客户端的互操作性,因为我们拥有并控制通信的每一端,并使用共享接口模式将数据传输到任一端。因此,我对任何符合使用带有消息安全性的 wsHttp 来传输使用 NetDataContractSerializer 序列化的对象图的限制的想法持开放态度,我更喜欢不需要大幅更改现有服务和周围基础设施的解决方案。

相关资源:

我也对可以对这些数据进行的任何类型的压缩感兴趣,但是一旦我可以转换到 .NET 4.0 以便客户端将自动支持 gzip,我可能最好在传输级别执行此操作标题,如果我理解正确的话。

更新(2010-06-29):

关于我如何得出缓冲消息太大导致我的问题的结论的一些历史。最初我在测试时看到了下面的CommunicationException

基础连接已关闭:连接意外关闭。

最终,在运行此程序并进行更多日志记录后,我发现导致指定消息出现问题的底层InsufficientMemoryException异常。

未能分配 268435456 字节的托管内存缓冲区。可用内存量可能很低。

这源于以下方法。

System.ServiceModel.Diagnostics.Utility.AllocateByteArray(Int32 大小)

所以换句话说,失败来自分配数组。将序列化的相同数据写入磁盘时,它占用大约 146MB,如果我将其减半,那么我将停止收到错误,但是我没有深入研究破坏缓冲区的特定阈值以及它是否特定于我的系统或不是。

更新(2010-12-06):

我想此时我正在为以下内容寻找一些澄清。我的理解是,默认情况下,具有消息安全性的 WCF wsHttp 在将响应发送回客户端之前需要在服务器上缓冲整条消息(通常是我返回的整组数据),从而导致我的问题。

可能的解决方案:

  • 限制数据大小 - 通过使用某种形式的压缩、编码或限制使用某种类似分页的方法返回的实际数据,以避免消耗传出缓冲区的最大容量。
  • 流式传输 - 允许通过 WCF 以流式传输方式发送大量数据,但这与 wsHttp 或 MessageSecurity 不兼容,因为这些技术需要缓冲所有数据。
  • 分块通道 - 允许将数据分解为单独的消息,但此时我不确定这对服务合同设计的限制,以及我是否仍然可以将 wsHttp 与消息绑定一起使用。

限制我可以返回的数据只能在一定程度上起作用,并且与 Streaming 选项一样,这些选项需要对 WCF 服务调用之外的许多较低级别的工作进行编码。所以我想我需要知道的是,分块通道的任何可能实现是否可以通过允许将一组数据分解为服务器上的单独消息,然后在客户端上拼凑在一起,从而避开大消息问题这样我就不必更改现有服务合同的接口/形状,并且该过程几乎对每个服务实现的客户端和服务器部分隐藏,同时仍使用消息安全性和 wsHttp。如果分块通道将要求我重写我的服务合同以公开流,那么我不会' 看不出这与 Streaming 解决方案有何不同。如果有人可以简单地为我回答这些问题,我将奖励他们并将其标记为答案。

4

5 回答 5

3

protobuf-net 通常对大多数数据具有显着的空间节省(如:数量级),并且可以附加到 WCF。不幸的是,目前它不支持完整的图表,只支持树。但是,我有计划,我根本没有时间实施。我不能保证任何事情,但我可以尝试早点完成这项工作。

否则; 可能有一些方法可以调整现有代码以使用树而不是图形。

于 2010-12-04T10:40:44.480 回答
2

我曾经实现向/从 wcf 传递大文本。我的三角是将其转换为流并使用 GZipStream 对其进行压缩,然后将其作为字节 [] 发送,幸运的是它从未超过 10 MB。

在你的情况下,我建议做碎片。将序列化对象转换为字节[],然后合并并解压缩

int transferSize = 5000000; // 5MB
byte[] compressed = ...;
var mem = new System.IO.MemoryStream(compressed);

for(int i = 0; i < compressed .length; i+= transferSize )
{
    byte[] buffer = new byte[transferSize];
    mem.Read(buffer, i, compressed);
    mem.Flush();
    sendFragmentToWCF(buffer);
}

编辑 2010 年 12 月 8 日

好的,根据我的理解,情况是客户端通过 WCF 下载一些大型序列化对象。我没有特别测试这个解决方案,但猜想它应该可以工作。关键是将序列化对象保存到文件并使用响应传输该文件。

[WebMethod]
public void GetSerializedObject()
{
    string path = @"X:\temp.tmp";

    var serializer = new  System.Runtime.Serialization.NetDataContractSerializer();
    var file = new System.IO.FileStream(path, System.IO.FileMode.CreateNew);

    try
    {
        serializer.Serialize(file, ...);
        this.Context.Response.TransmitFile(path);
        this.Context.Response.Flush();
    }
    finally
    {
        file.Flush();
        file.Close();
        file.Dispose();
        System.IO.File.Delete(path);
    }
}

WCF 应该自动进行文件流式传输,并且您不必担心序列化对象的大小,因为我们使用文件传输。不要忘记配置响应限制。

于 2010-12-07T20:44:04.433 回答
2

如果您仍想使用 Message Security,我建议您使用 MTOM 来优化传输消息所需的网络带宽,以及在应用安全性时使用更小的内存缓冲区的分块通道。否则,WCF 将尝试将整个消息加载到内存中以应用安全性,因此您将获得内存不足异常。

于 2010-06-30T14:31:34.050 回答
0

一些较轻但不能保证的解决方案是

  • 使用 theDataContractSerializer代替,因为您拥有双方。这不需要非常大的嵌入类型信息。
  • 在我问的一个问题[DataMember(EmitDefaultValue = false)]中讨论过的使用- 再次因为你拥有双方;这样做会减少一些消息大小(多少当然取决于图中有多少字段是默认值)。
  • 使用[DataContract(IsReference=true)],特别是如果您有许多重复的值对象或引用数据
  • 在服务器上使用某种限制来减少同时结果的内存压力

这些当然是权衡,例如可读性。

于 2011-12-05T23:25:49.543 回答
0

由于没有人把它放在那里,也许使用WebSockets长轮询技术可能能够解决这个问题。我只是简要地研究了这些解决方案,并没有围绕它们开发解决方案,但我想提出这些概念以供记录,如果时间允许,我将在稍后扩展我的答案。

基本思想是实现类似于 ChunkingChannel 示例如何工作的想法,但不需要全双工通道,这通常会破坏端口 80 基于 Web 的请求/响应模型,这是避免必须制作防火墙和其他所需的客户端的相关配置。

其他相关材料:

更新:在对此进行更多研究之后,似乎通过使用称为 NetHttpBinding 的 WebSockets,我本质上不会解决在 WCF 中使用具有消息安全性的 wsHttp 的原始请求。我将在这里保留我的答案,但作为可能正在寻找替代方案的其他人的信息。

于 2011-12-12T18:04:49.143 回答