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

C#在Lambda中实现S3同桶多对象打zip转存的方法

C# Lambda 实现S3目录打包ZIP上传方案

现有代码可优化点

  • 检查BatchDownload目录是否存在的逻辑放在对象遍历循环内,会重复发起S3请求,无意义消耗接口配额和执行时间,该逻辑只需在打包前执行1次
  • 调用异步接口时用.Result同步等待,在Lambda运行环境下有概率出现线程池死锁,建议全程用await异步写法
  • 单次调用ListObjectsAsync最多返回1000个对象,Download目录下文件数超过阈值时会出现漏打包,需要兼容分页遍历逻辑
  • 提前拉取所有对象键存入列表再处理,会产生不必要的内存开销,可在遍历对象时直接做流处理

核心实现思路

不要将S3对象全量下载到Lambda本地临时目录后再打包,全程使用流对接S3响应流、ZIP写入流、S3上传流,可大幅降低内存占用和执行耗时。注意Lambda内存配置需要大于待打包文件的总大小,若单包预期超过1G,建议改用S3 Batch Operation做离线打包,不要用同步Lambda执行。

完整可运行代码如下,不需要单独缓存所有对象键,边遍历S3对象边写入ZIP:

using Amazon.S3;
using Amazon.S3.Model;
using System.IO.Compression;

// 配置项,建议放到Lambda环境变量里
private const string BucketName = "你的存储桶名";
private const string SourcePrefix = "Download/";
private const string TargetPrefix = "BatchDownload/";
private readonly IAmazonS3 _s3Client = new AmazonS3Client(); // Lambda环境下默认用执行角色权限,不需要手动传密钥

public async Task<string> Handler()
{
    // 1. 提前检查目标目录是否存在,不存在则创建(S3目录本质是0字节前缀对象)
    var listTargetDirReq = new ListObjectsV2Request
    {
        BucketName = BucketName,
        Prefix = TargetPrefix,
        MaxKeys = 1
    };
    var targetDirResp = await _s3Client.ListObjectsV2Async(listTargetDirReq);
    if (targetDirResp.S3Objects.Count == 0)
    {
        await _s3Client.PutObjectAsync(new PutObjectRequest
        {
            BucketName = BucketName,
            Key = TargetPrefix
        });
    }

    // 2. 初始化ZIP流,总文件大小小于500M用MemoryStream即可,超过的话替换为FileStream写到/tmp目录
    var zipFileName = $"download-bundle-{DateTime.UtcNow:yyyyMMddHHmmss}.zip";
    using var zipOutputStream = new MemoryStream();
    using (var zipArchive = new ZipArchive(zipOutputStream, ZipArchiveMode.Create, leaveOpen: true))
    {
        // 3. 分页遍历源目录下所有对象,不需要提前存对象键列表
        var listSourceReq = new ListObjectsV2Request
        {
            BucketName = BucketName,
            Prefix = SourcePrefix
        };
        do
        {
            var sourceResp = await _s3Client.ListObjectsV2Async(listSourceReq);
            foreach (var s3Object in sourceResp.S3Objects)
            {
                // 跳过目录本身(Key以/结尾的对象是目录占位符,不需要打包)
                if (s3Object.Key.EndsWith("/")) continue;

                // 拉取当前对象的流
                var getObjResp = await _s3Client.GetObjectAsync(BucketName, s3Object.Key);
                // ZIP内的文件名去掉源前缀,避免ZIP里套一层Download目录
                var entryName = s3Object.Key.Substring(SourcePrefix.Length);
                var zipEntry = zipArchive.CreateEntry(entryName, CompressionLevel.Fastest);
                await using var entryStream = zipEntry.Open();
                // 直接把S3响应流复制到ZIP条目流,不落地到本地磁盘
                await getObjResp.ResponseStream.CopyToAsync(entryStream);
            }
            listSourceReq.ContinuationToken = sourceResp.NextContinuationToken;
        } while (listSourceReq.IsTruncated); // 还有分页的话继续遍历
    }

    // 4. 流位置重置到开头,上传ZIP到目标目录
    zipOutputStream.Position = 0;
    var putZipReq = new PutObjectRequest
    {
        BucketName = BucketName,
        Key = $"{TargetPrefix}{zipFileName}",
        InputStream = zipOutputStream,
        ContentType = "application/zip"
    };
    await _s3Client.PutObjectAsync(putZipReq);

    return $"打包完成,ZIP路径:s3://{BucketName}/{TargetPrefix}{zipFileName}";
}

额外注意事项

  • 代码用ListObjectsV2替代旧版ListObjects接口,是AWS官方推荐的版本,性能和稳定性更好
  • 压缩级别用Fastest平衡压缩率和执行速度,Lambda按执行时长计费,压缩级别太高会拉长执行时间增加成本
  • 如果需要打包后删除源Download目录下的文件,在ZIP上传成功后遍历源对象批量调用DeleteObjectAsync即可,不要在打包前删除避免文件丢失
  • Lambda默认临时目录/tmp有512M~10G的存储空间(和内存配置挂钩),打包大文件时把MemoryStream替换为new FileStream("/tmp/temp.zip", FileMode.Create)即可,内存占用会更低

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 14:57:20