序列化/反序列化大型数据集
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。
但是请注意,DataSet
和 DataTable
本质上不是优化的最佳候选者。有很多很棒的序列化工具可以在打包数据方面做得更好很多,但它们总是需要强类型模型,即List<SomeSpecificType>
,其中SomeSpecificType
是 POCO/DTO 类。甚至 WCF 也只能勉强容忍DataTable
/DataSet
。所以如果你可以摆脱对DataTable
/DataSet
的依赖:我强烈建议这样做。
另一种选择是将数据作为Stream
发送。我很确定 WCF 本身就支持这一点,但这在理论上可以让您拥有一个实际上更大的不同 Stream
(not MemoryStream
)。作为一种廉价的选择,您可以使用临时文件作为暂存区域,但如果可行,您可以研究将多个缓冲区拼接在一起的自定义内存流。
【讨论】:
“但是请注意,DataSet 和 DataTable 本质上不是优化的最佳候选者。” 注意这一点!请改用您自己的简单类。以上是关于序列化/反序列化大型数据集的主要内容,如果未能解决你的问题,请参考以下文章
从大型 ByteArrayOutputSteam 了解 Avro 反序列化