文章目录
- 1.MinIO 简介
- 2.为什么要分片上传?
- 3.实现思路
- 4.具体实现
- 初始化客户端
- 获取分片上传的预签名 URL
- 合并分片
- 中止合并
- 5.FAQ
- 端口错误
- 协议错误
- 参考文献
1.MinIO 简介
MinIO 是适用于 AI 的高性能对象存储系统。
MinIO 简单易用。简单性是 EB 级数据基础设施的基础 - 无论是在技术上还是在操作上。MinIO 使用和部署非常简单,没有其他对象存储可以让您在最快的时间内实现下载到生产环境的部署。
MinIO 提供高性能、与 AWS S3 兼容的对象存储系统,让你自己能够构建自己的云储存服务。
MinIO 原生支持 Kubernetes,它可用于每个独立的公共云、每个 Kubernetes 发行版、私有云和边缘的对象存储套件。
MinIO 是软件定义的,不需要购买其他任何硬件,在 GNU AGPL v3 下是 100% 开源的。
2.为什么要分片上传?
如果一个待上传的对象非常大,直接上传大文件会面临如下问题:
- 单线程上传速度慢,效率低。
- 如果发生网络抖动、程序崩溃等异常情况,导致上传过程中断,那么需要从头开始上传。
- 无法暂停,因为一旦中止,需要从头开始上传。
将大文件分片,然后逐片上传,可以进行多线程并发上传,提高吞吐量。
如果因为某些异常情况导致部分分片上传失败,那么其他已经上的传分片则无须重复上传,可以做到断点续传。
使用分片上传,可以暂停和恢复对象上传。
所以在上传大文件对象时,我们应该使用分片上传。
3.实现思路
实现大文件分片上传时,大体思路如下:
- 数据库中存放文件路径,所有文件保存在 MinIO 中,文件名即是文件的 MD5。
- 当用户上传文件时,首先判断该文件信息是否存在数据库中,如果存在则直接显示上传成功,若不存在则执行上传操作。
- 文件在真正上传之前先判断文件大小,太小的不需要创建分片上传任务,一次性上传即可。
- 后台调用 MinIO 的 API 创建分片上传任务(得到一个上传 ID ),并为该任务生成分片上传的预签名链接(上传地址列表)后返回给客户端,客户端将对应分片按照到对应的连接传递到 MinIO 中。
- 分片上传成功后更新进度信息。
- 所有分片上传结束后,通知后台,调用 MinIO 的 API 将当前任务的分片合并形成完整文件。
先不考虑小文件一次性上传的情况。对于大文件分片上传,三个关键步骤是:
- 创建分片上传任务,获取上传 ID。
- 生成分片上传预签名链接。
- 合并分片。
这三个操作均是由后台服务与 MinIO 交互。
分片上传则是客户端拿到分片上传预签名链接后,由客户端通过预签名链接与 MinIO 交互,将分片上传至 MinIO。
具体的上传交互方式如下图所示:
这里说一下上传 ID 与预签名链接的作用。
上传 ID 是分片上传的唯一标识符。无论您何时上传分段、列出分段、完成上传或停止上传,都必须包括此上传 ID。
默认情况下,所有对象和桶都是私有的。但是,我们可以使用预签名 URL 选择性地共享对象,或者允许用户通过预签名 URL 将对象上传到桶,而无需安全凭证或权限。考虑到安全性,一般情况下,预签名 URL 有有效期,在达到过期时间后会过期失效。
4.具体实现
这里以 MinIO 的 Go Client SDK minio-go 为例,介绍分片上传,后台服务需要完成的相关操作。
初始化客户端
首先下载 minio-go。
go get github.com/minio/minio-go/v7
MinIO 客户端需要指定以下 4 个参数才能接入 Amazon S3 兼容的对象存储。
// MinIO 地址。
endpoint := "play.min.io"
// 用户 ID。
keyID := "Q3AM3UQ867SPQQA43P2F"
// 用户密码。
accessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
// 是否使用 HTTPS。
useSSL := true
下面是一个初始化示例:
import "github.com/minio/minio-go/v7"
var MinIOClt *minio.Core
func init() {
endpoint := "10.0.0.1:9000"
keyID := "admin"
accessKey := "admin"
useSSL := false // 不使用 HTTPS
// Initialize minio client object.
var err error
MinIOClt, err = minio.NewCore(endpoint, &minio.Options{
Creds: credentials.NewStaticV4(keyID, accessKey, ""),
Secure: useSSL,
})
if err != nil {
log.Fatalln(err)
}
log.Printf("%#v\n", MinIOClt) // minioClient is now set up
}
这里没有使用 minio.Client 而是使用了 minio.Core。因为 minio.Core 暴露了更加底层的 S3 API,而这些 API 刚好是分片上传需要的。
获取分片上传的预签名 URL
后台需要根据客户端欲上传文件的总大小和分片大小计算出总的分片数,然后向 MinIO 获取每个分片上传的预签名 URL。
这里要注意,关于分片大小的确定,MinIO 规定分片大小范围是 5 MiB to 5 GiB,这也是 S3 API 的限制。详见 Thresholds and Limits。
在获取分片上传的预签名 URL 之前,需要创建一个 upload ID。在 minio-go 中,利用 minio.Core 的方法 NewMultipartUpload 可以创建 upload ID。
// NewMultipartUpload - Initiates new multipart upload and returns the new uploadID.
func (c Core) NewMultipartUpload(ctx context.Context, bucket, object string, opts PutObjectOptions) (uploadID string, err error)
在拿到 upload ID 后,需要为每个分片生成上传的预签名 URL,使用 minio.Client 方法 Presign 完成。
// Presign - returns a presigned URL for any http method of your choice along
// with custom request params and extra signed headers. URL can have a maximum
// expiry of upto 7days or a minimum of 1sec.
func (c *Client) Presign(ctx context.Context, method string, bucketName string, objectName string, expires time.Duration, reqParams url.Values) (u *url.URL, err error)
每个预签名链接的 Query,需要携带 upload ID 和 part Number。
part Number 是每个分片的唯一编号,取值范围是 1 至 10,000。part Number 可以不连续,但要唯一。
params := url.Values{
"uploadId": []string{uploadID},
"partNumber": []string{strconv.Itoa(partNum)},
}
合并分片
当客户端完通过预签名 URL 将所有分片上传完成后,通知后台服务。然后后台再调用 MinIO 的 API Core.CompleteMultipartUpload,将分片合并成最终的对象。
// CompleteMultipartUpload - Concatenate uploaded parts and commit to an object.
func (c Core) CompleteMultipartUpload(ctx context.Context, bucket, object, uploadID string, parts []CompletePart, opts PutObjectOptions) (UploadInfo, error)
注意,传入所有分片的 part Number 时,需要对其进行升序排序,不然会报 InvalidPartOrder 错误。详见 S3 CompleteMultipartUpload。
中止合并
如果想中途取消对象的上传,那么需要通知 MinIO 将分片进行清理。
使用到的 API 是 Core.AbortMultipartUpload 方法。
// AbortMultipartUpload - Abort an incomplete upload.
func (c Core) AbortMultipartUpload(ctx context.Context, bucket, object, uploadID string) error
5.FAQ
端口错误
访问 MinIO API 报了如下错误。
S3 API Request made to Console port. S3 Requests should be sent to API port.
原因是使用了错误的 API 端口。提示内容是将 API 的请求发送到了控制台端口。检查一了一下,我确实在初始化 SQLite client 时,使用了控制台 Web UI 的端口。MinIO 的 API 端口缺省是 9000,一般换成 9000 即可。
协议错误
访问 MinIO API 报了如下错误。
Get XXX: http: server gave HTTP response to HTTPS client
其中 XXX 为 URL 表示的资源。
MinIO 部署默认以 HTTP 方式对外提供服务,如果在初始化客户端时使用了 HTTPS,那么就会报上面的错误。将初始化客户端传入的参数 minio.Options 的 Secure 字段置为 false 即可。
参考文献
MinIO
github.com/minio/minio-go
Uploading and copying objects using multipart upload - AmazonS3 userguide
Using presigned URLs - AmazonS3 userguide
Minio入门系列【19】断点续传和断点下载实现方案