如何在C#中高效序列化大对象、压缩并上传至Azure Blob存储?
高效序列化大对象并压缩上传至Azure Blob的流式解决方案
这绝对是大对象处理中常见的坑——一次性把整个序列化结果加载到内存必然会触发OutOfMemoryException,尤其是对象体积较大的时候。解决方案的核心是全程采用流式处理,让数据在序列化、压缩、上传的环节中"流动",而不是在内存中囤积完整副本。
核心思路
我们会把三个环节串联成一条流管道:
- 直接将对象序列化到压缩流中(而非先转成字符串/字节数组)
- 压缩流的输出直接接入Azure Blob的上传流
- 所有操作都是增量处理,内存中仅保留小部分缓冲区数据
实现方案(两种序列化库可选)
方案1:使用Newtonsoft.Json(JSON.Net)
适合仍在使用.NET Framework或依赖JSON.Net特性的场景:
using System.IO.Compression; using Newtonsoft.Json; using Azure.Storage.Blobs; public async Task SerializeCompressAndUploadLargeObjectAsync( object largeObject, string azureConnectionString, string containerName, string blobName) { // 初始化Blob客户端 var blobClient = new BlobClient(azureConnectionString, containerName, blobName); // 打开Blob的上传流,允许覆盖现有文件 using var uploadStream = await blobClient.OpenWriteAsync(overwrite: true); // 创建GZip压缩流,设置leaveOpen=true避免提前关闭上传流 using var gzipStream = new GZipStream(uploadStream, CompressionMode.Compress, leaveOpen: true); // 创建JSON写入器,直接写入压缩流 using var streamWriter = new StreamWriter(gzipStream, Encoding.UTF8, leaveOpen: true); using var jsonWriter = new JsonTextWriter(streamWriter); // 序列化对象到流(增量处理,不会一次性加载全部内容) var serializer = new JsonSerializer(); serializer.Serialize(jsonWriter, largeObject); // 确保所有缓冲区数据都被写入底层流 await jsonWriter.FlushAsync(); await streamWriter.FlushAsync(); await gzipStream.FlushAsync(); }
方案2:使用System.Text.Json(推荐.NET Core/.NET 5+)
System.Text.Json原生支持流式序列化,代码更简洁且性能更优:
using System.IO.Compression; using System.Text.Json; using Azure.Storage.Blobs; public async Task SerializeCompressAndUploadLargeObjectAsync( object largeObject, string azureConnectionString, string containerName, string blobName) { var blobClient = new BlobClient(azureConnectionString, containerName, blobName); using var uploadStream = await blobClient.OpenWriteAsync(overwrite: true); using var gzipStream = new GZipStream(uploadStream, CompressionMode.Compress, leaveOpen: true); // 直接异步序列化对象到压缩流 await JsonSerializer.SerializeAsync( gzipStream, largeObject, new JsonSerializerOptions { WriteIndented = false, // 关闭缩进减少体积 DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull // 忽略空值进一步优化 }); }
关键注意事项
- 流的
leaveOpen参数:必须设置为true,避免上层流关闭时提前终止底层的Blob上传流。 - 压缩级别调整:可以通过
GZipStream的重载指定CompressionLevel,比如CompressionLevel.Optimal(默认)或CompressionLevel.Fastest,平衡压缩率和速度。 - 分块上传优化:如果对象超大型(比如GB级),可以在
OpenWriteAsync中传入BlobOpenWriteOptions指定BlockSize,让Azure SDK更高效地分块上传。 - 错误处理:建议添加
try-catch块确保流在异常时正确释放,避免Blob出现不完整数据。 - 内存验证:可以使用.NET内存诊断工具(比如dotMemory)对比流式方案与原方案的内存占用,你会发现流式方案的内存峰值极低。
为什么原方案会OOM?
原流程序列化字符串→转字节数组→UploadText的每一步都会把完整数据加载到内存中:
- 序列化字符串时,整个对象的JSON表示会被存入内存字符串
- 转字节数组时,又会创建一份与字符串体积相当的字节数组副本
UploadText内部还会再次转换为字节数组上传
这三次内存拷贝对于大对象来说,很容易突破内存限制。
内容的提问来源于stack exchange,提问作者Minhaz
相关产品推荐
相关产品推荐

