序列化/反序列化大型数据集

Posted

技术标签:

【中文标题】序列化/反序列化大型数据集【英文标题】:Serialize/Deserialize Large DataSet 【发布时间】:2018-08-05 17:23:25 【问题描述】:

我有一个向服务器发送查询请求的报告工具。服务器完成查询后,将结果发送回请求报告工具。通信是使用 WCF 完成的。

存储在 DataSet 对象中的查询数据非常大,通常大约 100mb 大。

为了加快传输我序列化(BinaryFormatter)并压缩DataSet。服务器和报告工具之间传输的对象是一个字节数组。

但是,在几次请求之后,报告工具在尝试反序列化 DataSet 时会抛出 OutOfMemoryException。调用时抛出异常:

dataSet = (DataSet) formatter.Deserialize(dstream);

dstream是用于解压传输的压缩字节数组的DeflateStream。

当从流中创建字节数组时,在 formatter.Deserialize 的子调用中发生异常。

有没有其他的二进制序列化方式有更好的机制来防止这个异常?

实施:

DataSet的序列化和压缩方法(服务端使用)

public static byte[] Compress(DataSet dataSet)

    using (var input = new MemoryStream())
    
        var binaryFormatter = new BinaryFormatter();
        binaryFormatter.Serialize(input, dataSet);

        using (var output = new MemoryStream())
        
            using (var compressor = new DeflateStream(output, CompressionLevel.Optimal))
            
                input.Position = 0;

                var buffer = new byte[1024];

                int read;

                while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
                    compressor.Write(buffer, 0, read);

                compressor.Close();

                return output.ToArray();
            
        
    

DataSet的解压反序列化方法(报表工具使用)

public static DataSet Decompress(byte[] data)

    DataSet dataSet;

    using (var input = new MemoryStream(data))
    
        using (var dstream = new DeflateStream(input, CompressionMode.Decompress))
        
            var formatter = new BinaryFormatter();
            dataSet = (DataSet) formatter.Deserialize(dstream);
        
    

    return dataSet;

堆栈跟踪:

at System.Array.InternalCreate(Void* elementType, Int32 rank, Int32* pLengths, Int32* pLowerBounds)
at System.Array.CreateInstance(Type elementType, Int32 length)
at System.Array.UnsafeCreateInstance(Type elementType, Int32 length)
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ParseArray(ParseRecord pr)
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ParseObject(ParseRecord pr)
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Parse(ParseRecord pr)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadArray(BinaryHeaderEnum binaryHeaderEnum)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
at DRX.PTClientMonitoring.Infrastructure.Helper.DataSetCompressor.Decompress(Byte[] data) in c:\_develop\PTClientMonitoringTool\PTClientMonitoringTool\Source\DRX.PTClientMonitoring.Infrastructure\Helper\DataSetCompressor.cs:line 51
at DRX.PTClientMonitoring.Reporting.ViewModels.ShellViewModel.<>c__DisplayClassf.<ExecudeDefinedQuery>b__4() in c:\_develop\PTClientMonitoringTool\PTClientMonitoringTool\Source\DRX.PTClientMonitoring.Reporting\ViewModels\ShellViewModel.cs:line 347

【问题讨论】:

如果能分享相关代码会有帮助吗? 在发送端,您是否将dataSet.RemotingFormat = SerializationFormat.Binary;设置为推荐的here?另外,您可以分享发生内存不足异常的回溯吗?我的印象是BinaryFormatter 通常会以增量方式读取流。 看看服务器和报告工具使用的方法。实际反序列化之前的垃圾回收调用无法阻止异常。我使用的是 DataSet,因为它包含多个表,具体取决于报告工具的查询请求。 堆栈跟踪已添加。我已经设置了RemotingFormat,但是在一定数量的请求之后仍然会出现异常。 那么如果我冒昧地猜测一下,那是您在large object heap 上创建了太多对象,最终导致内存碎片和内存不足错误。大的byte[] data 数组是一个明显的候选者。有什么方法可以直接将DataSet 流式传输,而不是加载到中间字节数组中?如果不把 Mark Gravell 的建议复制到某种 temp Stream. 【参考方案1】:

序列化前,设置:

yourDataSet.RemotingFormat = SerializationFormat.Binary;

这应该很有帮助。默认即使使用BinaryFormatter 也是xml。

但是请注意,DataSetDataTable本质上不是优化的最佳候选者。有很多很棒的序列化工具可以在打包数据方面做得更好很多,但它们总是需要强类型模型,即List&lt;SomeSpecificType&gt;,其中SomeSpecificType 是 POCO/DTO 类。甚至 WCF 也只能勉强容忍DataTable/DataSet。所以如果你可以摆脱对DataTable/DataSet的依赖:我强烈建议这样做。

另一种选择是将数据作为Stream 发送。我很确定 WCF 本身就支持这一点,但这在理论上可以让您拥有一个实际上更大的不同 Streamnot MemoryStream)。作为一种廉价的选择,您可以使用临时文件作为暂存区域,但如果可行,您可以研究将多个缓冲区拼接在一起的自定义内存流。

【讨论】:

“但是请注意,DataSet 和 DataTable 本质上不是优化的最佳候选者。” 注意这一点!请改用您自己的简单类。

以上是关于序列化/反序列化大型数据集的主要内容,如果未能解决你的问题,请参考以下文章

从大型 ByteArrayOutputSteam 了解 Avro 反序列化

中间件安全Weblogic反序列化&未授权&POC

漏洞通告Weblogic反序列化漏洞通告(CVE-2018-2628)

从 JSON 反序列化重复字典

15-PHP代码审计——yii 2.0.37反序列化漏洞

15-PHP代码审计——yii 2.0.37反序列化漏洞