低内存占用场景下Go服务大文件上传至Amazon S3的实现方案
大文件上传到S3的低内存占用优化方案
首先明确你关心的临时存储问题:Gin框架处理multipart/form-data请求时,默认会将小于32MB的请求体内容加载到内存,超过32MB的部分自动写入操作系统临时目录,不会全部占用内存,这个阈值可以通过engine.MaxMultipartMemory参数自定义调整。
下面是比「先写磁盘再分片上传」更优的两类方案:
方案1:无业务侧文件处理需求,优先使用S3预签名URL(最优解)
如果你的服务不需要对用户上传的文件做内容校验、转码、脱敏等中间处理,完全可以让用户直接上传到S3,不需要经过你的服务转发:
- 你的服务只需要根据用户的上传请求,调用S3接口生成带有效期的上传预签名URL,返回给前端
- 前端直接用这个URL把文件上传到S3,全程不占用你服务的带宽、内存、磁盘资源,也不存在两次上传的开销
方案2:必须经过服务转发的场景,用流式分片上传
如果确实需要服务做中间处理,也不需要先把整个文件存到本地磁盘,可以直接用AWS SDK自带的流式分片上传能力:
AWS Go SDK的manager.Uploader原生支持io.Reader输入,会自动把输入流切成分片并发上传到S3,你只需要把从请求里拿到的multipart.File直接传入即可,不需要预加载整个文件到内存,也不需要写入本地磁盘。
示例代码如下:
import ( "context" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/gin-gonic/gin" ) func uploadHandler(c *gin.Context) { // 按需调整Gin的multipart阈值,比如调整为10MB,超过部分自动落临时盘 // 可以在初始化Gin的时候全局设置:router.MaxMultipartMemory = 10 << 20 fileHeader, err := c.FormFile("file") if err != nil { // 错误处理 c.JSON(400, gin.H{"error": err.Error()}) return } file, err := fileHeader.Open() if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } defer file.Close() // 初始化S3上传器,按需配置分片大小、并发数 uploader := manager.NewUploader(s3.NewFromConfig(awsConfig), func(u *manager.Uploader) { u.PartSize = 8 * 1024 * 1024 // 分片大小设为8MB,最小支持5MB u.Concurrency = 3 // 并发上传数 }) // 直接传入file这个io.Reader,流式上传,不需要加载整个文件 _, err = uploader.Upload(context.TODO(), &s3.PutObjectInput{ Bucket: aws.String("你的存储桶名"), Key: aws.String(fileHeader.Filename), Body: file, }) if err != nil { c.JSON(500, gin.H{"error": "上传S3失败"}) return } c.JSON(200, gin.H{"message": "上传成功"}) }
这种方案的内存占用只和你设置的分片大小、并发数有关,哪怕上传GB级的文件,内存占用也能稳定在几十MB级别,完全不会出现内存溢出的问题。
额外优化建议
如果需要支持超大文件(超过10GB)的断点续传,可以额外对接S3的分段上传API,前端把文件切成分片,每个分片单独上传到你的服务或者直接用预签名URL上传到S3,所有分片上传完成后再调用S3的合并接口完成整个文件的上传。
内容的提问来源于stack exchange,提问作者Kaushal28
相关产品推荐
相关产品推荐

