OSS ON云盒提供分片上传功能,可以将待上传的文件(Object)分成多个碎片(Part)分别上传,上传完成之后再调用CompleteMultipartUpload接口将这些Part组合成一个Object。
前提条件
已创建云盒Bucket。具体操作,请参见创建云盒Bucket。
使用场景
大文件加速上传:当文件大小超过 5 GB 时,使用分片上传可实现并行上传多个Part,从而加快上传速度。
应对网络环境波动:在网络环境不佳的情况下,分片上传尤为有利。如果上传过程中出现失败,您只需重传失败的部分,避免重新上传整个文件的麻烦,节省了时间和带宽。
暂停和恢复上传:分片上传任务没有过期时间。您可以随时暂停和恢复分片上传,直到完成或取消分片上传。
文件大小不确定:在某些场景中,例如视频监控等行业应用中,文件的最终大小可能不确定。使用分片上传可以让您在未知文件大小的情况下开始上传。
分片上传流程
分片上传(Multipart Upload)分为以下三个步骤:
|
使用限制
限制项 | 说明 |
单个文件的大小 | 不超过48.8TB |
Part数量 | 1~10,000个 |
单个Part大小 | 最小值为100 KB,最大值为5 GB。最后一个Part的大小允许小于100 KB。 |
单次ListParts请求返回的Part最大数量 | 1,000个 |
单次ListMultipartUploads请求返回的Multipart Upload事件最大数量 | 1,000个 |
注意事项
文件上传性能调优
如果您在上传大量文件时,在命名上使用了顺序前缀(如时间戳或字母顺序),可能会出现大量文件索引集中存储于存储空间中某个特定分区的情况。此时如果您的请求速率过大,会导致请求速率下降。建议您在上传大量文件时,不要使用顺序前缀的文件名。更多信息,请参见OSS性能最佳实践。
文件覆盖
上传同名文件会覆盖OSS中已有文件。您可以通过以下方式防止文件被意外覆盖:
开启版本控制功能
开启版本控制功能后,被覆盖的文件会以历史版本的形式保存下来,您可以随时恢复历史版本文件。更多信息,请参见云盒版本控制。
在上传请求中携带禁止覆盖同名文件的参数 :在上传请求的header中携带x-oss-forbid-overwrite参数,并指定其值为
true。当您上传的文件在OSS中存在同名文件时,该文件会上传失败,并返回FileAlreadyExists错误。更多信息,请参见InitiateMultipartUpload。
删除Part
分片上传过程被中断后,已上传的Part会一直保存在Bucket中。如果您不再需要这些Part,请通过生命周期规则自动删除。具体操作,请参见云盒生命周期规则。
操作步骤
使用阿里云SDK
支持通过Java、Python、Go SDK进行分片上传(Java SDK要求3.15.0及以上版本,Python SDK V2要求1.1.1及以上版本,Go SDK V2要求v1.2.1及以上版本)。云盒场景下,Python、Go SDK的操作调用方式与公共云OSS一致,创建客户端的方式请参见SDK支持。以下代码示例分别以Java、Python、Go SDK为例。
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
public class Demo {
public static void main(String[] args) throws Exception {
// 填写云盒Bucket的数据域名。
String endpoint = "https://cb-f8z7yvzgwfkl9q0h****.cn-hangzhou.oss-cloudbox.aliyuncs.com";
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
// 填写云盒Bucket名称,例如examplebucket。
String bucketName = "examplebucket";
// 填写云盒Bucket所在地域。
String region = "cn-hangzhou";
// 填写云盒ID。
String cloudBoxId = "cb-f8z7yvzgwfkl9q0h****";
// 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
String objectName = "exampledir/exampleobject.txt";
// 创建OSSClient实例。
// 当OSSClient实例不再使用时,调用shutdown方法以释放资源。
ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
conf.setSignatureVersion(SignVersion.V4);
OSS ossClient = OSSClientBuilder.create()
.endpoint(endpoint)
.credentialsProvider(new DefaultCredentialProvider(credentialsProvider.getCredentials()))
.clientConfiguration(conf)
.region(region)
.cloudBoxId(cloudBoxId)
.build();
try {
// 创建InitiateMultipartUploadRequest对象。
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);
// 如果需要在初始化分片时设置请求头,请参考以下示例代码。
// ObjectMetadata metadata = new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// 指定该Object的网页缓存行为。
// metadata.setCacheControl("no-cache");
// 指定该Object被下载时的名称。
// metadata.setContentDisposition("attachment;filename=oss_MultipartUpload.txt");
// 指定该Object的内容编码格式。
// metadata.setContentEncoding(OSSConstants.DEFAULT_CHARSET_NAME);
// 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。
// metadata.setHeader("x-oss-forbid-overwrite", "true");
// 指定上传该Object的每个part时使用的服务器端加密方式。
// metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION, ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION);
// 指定Object的加密算法。如果未指定此选项,表明Object使用AES256加密算法。
// metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_DATA_ENCRYPTION, ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION);
// 指定KMS托管的用户主密钥。
// metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION_KEY_ID, "9468da86-3509-4f8d-a61e-6eab1eac****");
// 指定Object的存储类型。
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard);
// 指定Object的对象标签,可同时设置多个标签。
// metadata.setHeader(OSSHeaders.OSS_TAGGING, "a:1");
// request.setObjectMetadata(metadata);
// 初始化分片。
InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
// 返回uploadId,它是分片上传事件的唯一标识。您可以根据该uploadId发起相关的操作,例如取消分片上传、查询分片上传等。
String uploadId = upresult.getUploadId();
// partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
List<PartETag> partETags = new ArrayList<PartETag>();
// 每个分片的大小,用于计算文件有多少个分片。单位为字节。
final long partSize = 1 * 1024 * 1024L; //1 MB。
// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
final File sampleFile = new File("D:\\localpath\\examplefile.txt");
long fileLength = sampleFile.length();
int partCount = (int) (fileLength / partSize);
if (fileLength % partSize != 0) {
partCount++;
}
// 遍历分片上传。
for (int i = 0; i < partCount; i++) {
long startPos = i * partSize;
long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
InputStream instream = new FileInputStream(sampleFile);
// 跳过已经上传的分片。
instream.skip(startPos);
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(bucketName);
uploadPartRequest.setKey(objectName);
uploadPartRequest.setUploadId(uploadId);
uploadPartRequest.setInputStream(instream);
// 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。
uploadPartRequest.setPartSize(curPartSize);
// 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。
uploadPartRequest.setPartNumber( i + 1);
// 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。
UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
// 每次上传分片之后,OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
partETags.add(uploadPartResult.getPartETag());
}
// 创建CompleteMultipartUploadRequest对象。
// 在执行完成分片上传操作时,需要提供所有有效的partETags。OSS收到提交的partETags后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
// 如果需要在完成分片上传的同时设置文件访问权限,请参考以下示例代码。
// completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.Private);
// 指定是否列举当前UploadId已上传的所有Part。如果通过服务端List分片数据来合并完整文件时,以上CompleteMultipartUploadRequest中的partETags可为null。
// Map<String, String> headers = new HashMap<String, String>();
// 如果指定了x-oss-complete-all:yes,则OSS会列举当前UploadId已上传的所有Part,然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
// 如果指定了x-oss-complete-all:yes,则不允许继续指定body,否则报错。
// headers.put("x-oss-complete-all","yes");
// completeMultipartUploadRequest.setHeaders(headers);
// 完成分片上传。
CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
System.out.println(completeMultipartUploadResult.getETag());
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
}# -*- coding: utf-8 -*-
import os
import alibabacloud_oss_v2 as oss
# 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
# 使用SDK的默认配置。
cfg = oss.config.load_default()
cfg.credentials_provider = credentials_provider
# 设置Region为云盒所在的地域,例如cn-hangzhou。
cfg.region = "cn-hangzhou"
# 设置云盒ID。设置云盒ID后,SDK会自动将签名产品切换为oss-cloudbox。
cfg.cloud_box_id = "cb-f8z7yvzgwfkl9q0h****"
# 设置访问域名,格式为CloudboxId.Region.oss-cloudbox.aliyuncs.com。
cfg.endpoint = "cb-f8z7yvzgwfkl9q0h****.cn-hangzhou.oss-cloudbox.aliyuncs.com"
# 创建OSS客户端。
client = oss.Client(cfg)
# 填写Bucket名称,例如examplebucket。
bucket_name = "examplebucket"
# 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
object_name = "exampledir/exampleobject.txt"
# 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
file_path = "D:\\localpath\\examplefile.txt"
# 初始化分片。返回的upload_id是分片上传事件的唯一标识。您可以根据该upload_id发起相关操作,例如取消分片上传、查询分片上传等。
init_result = client.initiate_multipart_upload(oss.InitiateMultipartUploadRequest(
bucket=bucket_name,
key=object_name,
))
upload_id = init_result.upload_id
# 每个分片的大小,用于计算文件有多少个分片。单位为字节。此处设置为1 MB。
part_size = 1 * 1024 * 1024
data_size = os.path.getsize(file_path)
part_number = 1
upload_parts = []
# 遍历分片上传。除了最后一个分片没有大小限制,其他的分片最小为100 KB。
with open(file_path, 'rb') as f:
for start in range(0, data_size, part_size):
n = part_size
if start + n > data_size:
n = data_size - start
reader = oss.io_utils.SectionReader(oss.io_utils.ReadAtReader(f), start, n)
up_result = client.upload_part(oss.UploadPartRequest(
bucket=bucket_name,
key=object_name,
upload_id=upload_id,
part_number=part_number,
body=reader,
))
# 每次上传分片之后,返回结果中包含该分片的ETag,保存在upload_parts中。
upload_parts.append(oss.UploadPart(part_number=part_number, etag=up_result.etag))
part_number += 1
# 按分片号从小到大排序。
parts = sorted(upload_parts, key=lambda p: p.part_number)
# 完成分片上传。OSS收到提交的所有分片的ETag后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。
result = client.complete_multipart_upload(oss.CompleteMultipartUploadRequest(
bucket=bucket_name,
key=object_name,
upload_id=upload_id,
complete_multipart_upload=oss.CompleteMultipartUpload(
parts=parts
),
))
print(f'etag: {result.etag}')package main
import (
"context"
"io"
"log"
"os"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)
func main() {
// 加载SDK的默认配置,并设置访问凭证、地域、云盒ID及访问域名。
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
cfg := oss.LoadDefaultConfig().
WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
// 设置Region为云盒所在的地域,例如cn-hangzhou。
WithRegion("cn-hangzhou").
// 设置访问域名,格式为CloudboxId.Region.oss-cloudbox.aliyuncs.com。
WithEndpoint("cb-f8z7yvzgwfkl9q0h****.cn-hangzhou.oss-cloudbox.aliyuncs.com").
// 设置云盒ID。设置云盒ID后,SDK会自动将签名产品切换为oss-cloudbox。
WithCloudBoxId("cb-f8z7yvzgwfkl9q0h****")
// 创建OSS客户端。
client := oss.NewClient(cfg)
// 填写Bucket名称,例如examplebucket。
bucketName := "examplebucket"
// 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
objectName := "exampledir/exampleobject.txt"
// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
filePath := "D:\\localpath\\examplefile.txt"
// 初始化分片。返回的UploadId是分片上传事件的唯一标识。
initResult, err := client.InitiateMultipartUpload(context.TODO(), &oss.InitiateMultipartUploadRequest{
Bucket: oss.Ptr(bucketName),
Key: oss.Ptr(objectName),
})
if err != nil {
log.Fatalf("failed to initiate multipart upload %v", err)
}
uploadId := initResult.UploadId
// 打开本地文件。
file, err := os.Open(filePath)
if err != nil {
log.Fatalf("failed to open file %v", err)
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
log.Fatalf("failed to stat file %v", err)
}
fileSize := stat.Size()
// 每个分片的大小,用于计算文件有多少个分片。单位为字节。此处设置为1 MB。
partSize := int64(1 * 1024 * 1024)
partCount := int(fileSize / partSize)
if fileSize%partSize != 0 {
partCount++
}
var parts []oss.UploadPart
// 遍历分片上传。除了最后一个分片没有大小限制,其他的分片最小为100 KB。
for i := 0; i < partCount; i++ {
startPos := int64(i) * partSize
curPartSize := partSize
if i+1 == partCount {
curPartSize = fileSize - startPos
}
partRequest := &oss.UploadPartRequest{
Bucket: oss.Ptr(bucketName),
Key: oss.Ptr(objectName),
// 设置分片号。取值范围是1~10000。
PartNumber: int32(i + 1),
UploadId: uploadId,
Body: io.NewSectionReader(file, startPos, curPartSize),
}
partResult, err := client.UploadPart(context.TODO(), partRequest)
if err != nil {
log.Fatalf("failed to upload part %v", err)
}
// 每次上传分片之后,返回结果中包含该分片的ETag,保存在parts中。
parts = append(parts, oss.UploadPart{
PartNumber: partRequest.PartNumber,
ETag: partResult.ETag,
})
}
// 完成分片上传。OSS收到提交的所有分片的ETag后,会将这些分片组合成一个完整的文件。
result, err := client.CompleteMultipartUpload(context.TODO(), &oss.CompleteMultipartUploadRequest{
Bucket: oss.Ptr(bucketName),
Key: oss.Ptr(objectName),
UploadId: uploadId,
CompleteMultipartUpload: &oss.CompleteMultipartUpload{
Parts: parts,
},
})
if err != nil {
log.Fatalf("failed to complete multipart upload %v", err)
}
log.Printf("etag: %v\n", oss.ToString(result.ETag))
}使用命令行工具ossutil
使用命令行工具ossutil 2.0的高级命令cp(上传文件)上传文件时,如果文件较大,ossutil会自动使用分片上传方式。
ossutil cp D:/localpath/example.iso oss://examplebucket/desfolder/如需手动实现分片上传,您可以组合使用API级命令initiate-multipart-upload、upload-part、complete-multipart-upload。
相关API
以上操作方式底层基于API实现,如果您的程序自定义要求较高,您可以直接发起REST API请求。直接发起REST API请求需要手动编写代码计算签名。
关于初始化分片上传任务的接口说明,请参见InitiateMultipartUpload。
关于上传Part的接口说明,请参见UploadPart。
关于从一个已存在的Object中拷贝数据来上传一个Part的接口说明,请参见UploadPartCopy。
关于完成分片上传任务的接口说明,请参见CompleteMultipartUpload。
关于取消MultipartUpload事件并删除对应的Part的接口说明,请参见AbortMultipartUpload。
关于列举所有执行中的Multipart Upload事件的接口说明,请参见ListMultipartUploads。
关于列举所有已经上传成功的Part的接口说明,请参见ListParts。