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

如何使用Golang分块上传大字符串或字节数组至AWS S3?

Golang实现大字符串/字节数组分块上传至AWS S3

场景:从数据库导出特定数据并编码为JSON字符串,因数据量达数十亿字符,受内存限制无法一次性上传至AWS S3,需分块上传后生成完整可下载的JSON文件。

核心思路

利用AWS S3的**Multipart Upload(分块上传)**功能,将大数据拆分为多个小块依次上传,最后合并为单个文件。Golang可通过AWS SDK v2实现该流程,核心步骤为:初始化分块会话 → 分块上传数据 → 完成分块合并。

实现步骤及代码示例

1. 初始化S3客户端

先配置AWS凭证与区域,创建S3客户端实例:

import (
    "context"
    "fmt"
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/s3"
    "github.com/aws/aws-sdk-go-v2/service/s3/types"
    "io"
    "strings"
)

func initS3Client(ctx context.Context) (*s3.Client, error) {
    cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("your-region"))
    if err != nil {
        return nil, fmt.Errorf("加载配置失败: %w", err)
    }
    return s3.NewFromConfig(cfg), nil
}

2. 创建分块上传会话

生成Upload ID,作为本次分块上传的唯一标识:

func createMultipartUpload(ctx context.Context, client *s3.Client, bucket, key string) (string, error) {
    resp, err := client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
        Bucket:      aws.String(bucket),
        Key:         aws.String(key),
        ContentType: aws.String("application/json"),
    })
    if err != nil {
        return "", fmt.Errorf("初始化分块上传失败: %w", err)
    }
    return *resp.UploadId, nil
}

3. 分块上传数据

将大字符串拆分为固定大小的块(S3要求非最后块最小5MB),逐个上传并记录每个块的ETag:

const chunkSize = 5 * 1024 * 1024 // 5MB分块大小

func uploadChunks(ctx context.Context, client *s3.Client, bucket, key, uploadID, largeStr string) ([]types.CompletedPart, error) {
    var completedParts []types.CompletedPart
    partNumber := 1
    strReader := strings.NewReader(largeStr)
    buf := make([]byte, chunkSize)

    for {
        n, err := strReader.Read(buf)
        if err != nil && err != io.EOF {
            return nil, fmt.Errorf("读取分块失败: %w", err)
        }
        if n == 0 {
            break
        }

        // 上传当前分块
        resp, err := client.UploadPart(ctx, &s3.UploadPartInput{
            Bucket:     aws.String(bucket),
            Key:        aws.String(key),
            UploadId:   aws.String(uploadID),
            PartNumber: aws.Int32(int32(partNumber)),
            Body:       strings.NewReader(string(buf[:n])),
        })
        if err != nil {
            return nil, fmt.Errorf("上传第%d块失败: %w", partNumber, err)
        }

        completedParts = append(completedParts, types.CompletedPart{
            ETag:       resp.ETag,
            PartNumber: aws.Int32(int32(partNumber)),
        })
        partNumber++
    }

    return completedParts, nil
}

4. 完成分块合并

所有分块上传完成后,调用接口合并为完整文件:

func completeMultipartUpload(ctx context.Context, client *s3.Client, bucket, key, uploadID string, parts []types.CompletedPart) error {
    _, err := client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
        Bucket:   aws.String(bucket),
        Key:      aws.String(key),
        UploadId: aws.String(uploadID),
        MultipartUpload: &types.CompletedMultipartUpload{
            Parts: parts,
        },
    })
    if err != nil {
        return fmt.Errorf("完成分块合并失败: %w", err)
    }
    return nil
}

5. 整合流程主函数

func main() {
    ctx := context.Background()
    bucket := "your-bucket-name"
    key := "large-data.json"
    largeStr := "your-extremely-large-json-string" // 实际场景建议从数据库流式获取

    client, err := initS3Client(ctx)
    if err != nil {
        panic(err)
    }

    uploadID, err := createMultipartUpload(ctx, client, bucket, key)
    if err != nil {
        panic(err)
    }

    parts, err := uploadChunks(ctx, client, bucket, key, uploadID, largeStr)
    if err != nil {
        // 上传失败时清理未完成的分块资源
        _, abortErr := client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{
            Bucket:   aws.String(bucket),
            Key:      aws.String(key),
            UploadId: aws.String(uploadID),
        })
        if abortErr != nil {
            fmt.Printf("清理分块资源失败: %v\n", abortErr)
        }
        panic(err)
    }

    if err := completeMultipartUpload(ctx, client, bucket, key, uploadID, parts); err != nil {
        panic(err)
    }

    fmt.Println("分块上传完成")
}

关键优化建议

  • 流式处理规避内存堆积:不要将所有数据库数据拼接成大字符串,改为从数据库流式查询+流式JSON编码,直接将数据流拆分为分块上传,彻底解决内存限制问题。
  • 分块大小调优:S3允许分块范围为5MB-5GB,建议设置为10-100MB,平衡网络请求次数与单块上传稳定性。
  • 并发上传提升效率:使用goroutine并发上传多个分块,但需控制并发数避免触发AWS限流。
  • 断点续传支持:记录已成功上传的分块ETag与序号,若上传中断可从断点处继续,无需重新上传所有分块。
  • JSON格式合法性保障:若生成数组型JSON,需确保分块后整体结构正确——比如第一块开头加[,中间块结尾加,,最后块结尾加],避免合并后的JSON无效。
  • 错误时的资源清理:上传失败务必调用AbortMultipartUpload清理未完成的分块,避免S3存储无效资源产生额外费用。

内容的提问来源于stack exchange,提问作者Jan Švábík

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 12:16:01