You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中高效序列化大对象、压缩并上传至Azure Blob存储?

高效序列化大对象并压缩上传至Azure Blob的流式解决方案

这绝对是大对象处理中常见的坑——一次性把整个序列化结果加载到内存必然会触发OutOfMemoryException,尤其是对象体积较大的时候。解决方案的核心是全程采用流式处理,让数据在序列化、压缩、上传的环节中"流动",而不是在内存中囤积完整副本。

核心思路

我们会把三个环节串联成一条流管道:

  1. 直接将对象序列化到压缩流中(而非先转成字符串/字节数组)
  2. 压缩流的输出直接接入Azure Blob的上传流
  3. 所有操作都是增量处理,内存中仅保留小部分缓冲区数据

实现方案(两种序列化库可选)

方案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的每一步都会把完整数据加载到内存中:

  1. 序列化字符串时,整个对象的JSON表示会被存入内存字符串
  2. 转字节数组时,又会创建一份与字符串体积相当的字节数组副本
  3. UploadText内部还会再次转换为字节数组上传
    这三次内存拷贝对于大对象来说,很容易突破内存限制。

内容的提问来源于stack exchange,提问作者Minhaz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:40:16