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

如何在Golang中实现MinIO大文件分片续传?ComposeObject是否可行?

MinIO大文件分片上传续传方案答疑

分片保存大文件的思路完全正确

把大文件拆成固定大小的分片(比如你说的5MB)分别上传、最终合并,是处理大文件上传的标准方案,完全适配你的需求:

  • 解决单文件上传因网络波动、超时导致的全量重传问题,续传时只需补传未完成的分片
  • 5MB的分片尺寸刚好符合MinIO的要求(除最后一片外,分片最小需为5MB),在网络兼容性和传输效率之间平衡得很好
  • 分散单请求的负载,降低服务端的处理压力

ComposeObject是否适合实现续传?

可以用,但不是最优选择,更推荐用MinIO官方的Multipart Upload API来实现续传:

  1. ComposeObject的定位:它主要用于合并MinIO中已存在的多个独立对象,虽然能实现分片合并,但没有内置的分片上传状态跟踪机制,续传时你得自己完全维护所有分片的上传记录,出错后排查和恢复成本很高;另外它最多只能合并1000个对象,若大文件拆分后的分片数超过这个限制就会失效。
  2. Multipart Upload API的优势:这是S3兼容的标准分片上传协议,MinIO对其支持完善,原生适配续传场景:
    • 先通过NewMultipartUpload创建分片上传任务,获取唯一的UploadID
    • 分片上传用PutObjectPart,每个分片上传后会返回ETag,你需要把UploadID、已上传分片的编号和对应ETag记录下来(存在本地文件或数据库)
    • 续传时调用ListObjectParts拉取已上传的分片列表,跳过已完成的部分,直接从下一个分片开始上传
    • 所有分片传完后,调用CompleteMultipartUpload合并生成最终文件

简单实现示例(Multipart Upload方式)

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
)

func main() {
	ctx := context.Background()
	endpoint := "your-minio-endpoint"
	accessKey := "your-access-key"
	secretKey := "your-secret-key"
	useSSL := false

	// 初始化MinIO客户端
	client, err := minio.New(endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(accessKey, secretKey, ""),
		Secure: useSSL,
	})
	if err != nil {
		log.Fatalln(err)
	}

	bucket := "your-bucket"
	objName := "large-file.dat"
	filePath := "/path/to/your/large-file.dat"
	partSize := 5 * 1024 * 1024 // 5MB分片

	file, err := os.Open(filePath)
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()

	fileInfo, err := file.Stat()
	if err != nil {
		log.Fatalln(err)
	}
	fileSize := fileInfo.Size()

	// 检查是否存在未完成的分片上传任务
	var uploadID string
	var completedParts map[int]string = make(map[int]string)
	incompleteUploads, err := client.ListIncompleteUploads(ctx, bucket, objName, "", 0)
	if err != nil {
		log.Fatalln(err)
	}

	for _, upload := range incompleteUploads {
		if upload.Key == objName {
			uploadID = upload.UploadID
			// 拉取已上传的分片列表
			partsResp, err := client.ListObjectParts(ctx, bucket, objName, uploadID, 0, "", 1000)
			if err != nil {
				log.Fatalln(err)
			}
			for _, part := range partsResp.Parts {
				completedParts[part.PartNumber] = part.ETag
			}
			break
		}
	}

	// 若没有未完成任务,创建新的上传任务
	if uploadID == "" {
		var err error
		uploadID, err = client.NewMultipartUpload(ctx, bucket, objName, minio.PutObjectOptions{})
		if err != nil {
			log.Fatalln(err)
		}
	}

	// 开始上传分片(跳过已完成的)
	var completeParts []minio.CompletePart
	startPart := len(completedParts) + 1
	offset := int64((startPart - 1) * partSize)

	// 移动文件指针到起始位置
	_, err = file.Seek(offset, 0)
	if err != nil {
		log.Fatalln(err)
	}

	for partNum := startPart; offset < fileSize; partNum++ {
		remaining := fileSize - offset
		currentPartSize := partSize
		if remaining < int64(currentPartSize) {
			currentPartSize = int(remaining)
		}

		etag, err := client.PutObjectPart(ctx, bucket, objName, uploadID, partNum, file, int64(currentPartSize), minio.PutObjectPartOptions{})
		if err != nil {
			log.Fatalln(err)
		}

		completeParts = append(completeParts, minio.CompletePart{
			PartNumber: partNum,
			ETag:       etag,
		})
		fmt.Printf("分片 %d 上传完成,ETag: %s\n", partNum, etag)

		offset += int64(currentPartSize)
	}

	// 合并所有分片
	_, err = client.CompleteMultipartUpload(ctx, bucket, objName, uploadID, completeParts, minio.CompleteMultipartUploadOptions{})
	if err != nil {
		log.Fatalln(err)
	}

	fmt.Printf("文件 %s 上传合并完成\n", objName)
}

内容的提问来源于stack exchange,提问作者Акрам Азизмуродов

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 02:34:54