标准接口

简单上传方式只能上传小于5GB的对象,如果需要上传超过5GB的对象,您必须使用分片上传模式。此外,您也可以在如下的应用场景内(但不仅限于此),使用分片上传模式:

  • 上传超过100MB大小的文件。
  • 上传文件之前,无法确定上传文件的大小。
  • 需要较高的吞吐量(采用并发上传分片)。
  • 网络条件较差,和服务器之间的链接经常断开。

使用分片上传时,需要分为如下3个步骤:

  • 初始化一个分片上传任务,使用 InitiateMultipartUpload 接口
  • 使用 UploadPart 接口逐个或并行上传分片,或者使用 CopyPart 接口复制分片
  • 完成分片上传,使用 CompleteMultipartUpload 接口

初始化分片上传

使用Multipart Upload模式传输数据前,必须先初始化一个Multipart Upload事件,Multipart Upload事件可用于上传或复制对象。该操作会返回一个服务器创建的全局唯一的Upload ID,用于标识本次Multipart Upload事件。用户可以根据这个ID来发起相关的操作,如中止Multipart Upload、查询Multipart Upload等。

String bucketName = "<your-bucket-name>";
String objectKey = "<your-object-key>";
//指定桶与对象名,也可以在request中同时设置metadata,acl等对象的属性
InitiateMultipartUploadRequest initUploadRequest = new
        InitiateMultipartUploadRequest(bucketName, objectKey);
InitiateMultipartUploadResult initResult =
        s3.initiateMultipartUpload(initUploadRequest);
//用于区分分片上传的唯一标识,后续的操作中会使用该id
String uploadId = initResult.getUploadId();

上传分片

初始化一个Multipart Upload之后,可以根据指定的对象名和Upload ID来分片(Part)上传数据。每一个上传的Part都有一个标识它的号码——分片号(part number,范围是1~10000)。对于同一个Upload ID,该分片号不但唯一标识这一块数据,也标识了这块数据在整个文件内的相对位置,您需要在UploadPartRequest中指定Upload ID与分片号。如果用同一个分片号码,上传了新的数据,那么已有的这个分片的数据将被覆盖。除了最后一块part以外,其他的part最小为5MB;最后一块part没有大小限制。分片不需要按顺序上传,甚至可以在不同进程、不同机器上上传,在完成分片上传后服务端会按照分片号排序组成大文件。上传分片部分代码如下:

//分片大小为5M
long partSize = 5 * 1024 * 1024;
long filePosition = 0;
for (int partNum = 1; filePosition < contentLength; partNum++) {
    partSize = Math.min(partSize, (contentLength - filePosition));

    // 创建分片复制请求
    UploadPartRequest uploadRequest = new UploadPartRequest()
            .withBucketName(bucketName)
            .withKey(objectKey)
        	//uploadId为initiateMultipartUpload中返回值
            .withUploadId(uploadId)
            //设置分片号
            .withPartNumber(partNum)
            //设置文件数据偏移量
            .withFileOffset(filePosition)
        	//设置文件
            .withFile(file)
            //设置分片大小,最小为5MB
            .withPartSize(partSize);

    // 上传分片并把ETag放入list中.
    UploadPartResult uploadResult = s3.uploadPart(uploadRequest);
    partETags.add(uploadResult.getPartETag());
    filePosition += partSize;
}

复制分片

您可以初始化一个Multipart Upload后,通过copyPart接口分片复制某一个对象,您需要在CopyPartRequest中设置复制的源桶名对象名,目标桶名对象名,Upload ID来分片(Part)复制对象。与上传分片类似的,每一个复制的分片都有一个分片号(part number),您也需要在CopyPartRequest中设置分片号标识复制的分片。复制分片部分代码如下:

//分片大小为5M
long partSize = 5 * 1024 * 1024;
long bytePosition = 0;
int partNum = 1;
List<CopyPartResult> copyResponses = new ArrayList<CopyPartResult>();
while (bytePosition < objectSize) {
	long lastByte = Math.min(bytePosition + partSize - 1, objectSize - 1);
    
    // 创建分片复制请求
    CopyPartRequest copyRequest = new CopyPartRequest()
                //设置源桶和对象,目标桶和对象
                .withSourceBucketName(sourceBucketName)
                .withSourceKey(sourceObjectKey)
                .withDestinationBucketName(destBucketName)
                .withDestinationKey(destObjectKey)
        		//uploadId为initiateMultipartUpload中返回值
                .withUploadId(initResult.getUploadId())
                //设置分片复制范围
                .withFirstByte(bytePosition)
                .withLastByte(lastByte)
                //设置分片号
                .withPartNumber(partNum++);

    CopyPartResult copyPartResult = s3.copyPart(copyRequest);
    partETags.add(copyPartResult.getPartETag());
    bytePosition += partSize;
}

完成分片上传或复制

所有分片上传或复制完成后,需要调用 Complete Multipart Upload 来完成整个文件的 Multipart Upload。在执行该操作时,需要提供所有有效的分片列表(包括分片号和分片 ETAG );服务端收到提交的分片列表后,会逐一验证每个分片的有效性。当所有的数据Part验证通过后,服务端将把这些分片组合成一个完整的 Object。完成分片上传示例代码如下:

CompleteMultipartUploadRequest completeMultipartUploadRequest =
    new CompleteMultipartUploadRequest(bucketName, objectKey, uploadId, partETags);
//成功完成分片上传后,服务端会返回该对象的信息
CompleteMultipartUploadResult result =
s3.completeMultipartUpload(completeMultipartUploadRequest);

若是完成分片复制,CompleteMultipartUploadRequest 中 bucketName 和 objectKey 设置为目标的桶和对象名,示例代码如下:

//完成分片复制
CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(
    	//CompleteMultipartUploadRequest设置目标桶和对象名
        destBucketName,
        destObjectKey,
        initResult.getUploadId(),
        partETags);
s3.completeMultipartUpload(completeRequest);

列举分片

您可以通过 listParts 接口根据 Upload ID 获取所有已经上传成功的分片。默认每次请求返回的分片数为1000。

ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName,objectKey,UploadId);
PartListing partListing = s3.listParts(listPartsRequest);
for (PartSummary part : partListing.getParts()) {
    // 分片号,上传时候指定
    part.getPartNumber();
    // Part大小
    part.getSize();
    // Part的ETag
    part.getETag();
    // Part的最后修改上传
    part.getLastModified();
}

如果分片数大于1000,返回的 PartListing 中 isTruncated 为 true ,并可以根据 NextPartNumberMarker 为下一次请求的 list 的起始位置。

PartListing partListing;
ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, objectKey, uploadId);
do {
    partListing = s3.listParts(listPartsRequest);
    for (PartSummary part : partListing.getParts()) {
        // 分片号,上传时候指定
        part.getPartNumber();
        // Part大小
        part.getSize();
        // Part的ETag
        part.getETag();
        // Part的最后修改上传
        part.getLastModified();
    }
    listPartsRequest.setPartNumberMarker(partListing.getNextPartNumberMarker());
} while (partListing.isTruncated());

取消分片上传

该接口可以根据 Upload ID 中止对应的 Multipart Upload。当一个 Multipart Upload 被中止后,就不能再使用这个 Upload ID 做任何操作,已经上传的 Part 数据也会被删除。

//uploadId为initiateMultipartUpload中返回值
AbortMultipartUploadRequest abortMultipartUploadRequest = new AbortMultipartUploadRequest(bucketName, objectKey, uploadId);
s3.abortMultipartUpload(abortMultipartUploadRequest);

列举分片上传任务

您可以通过 listMultipartUploadsRequest 接口列举分片上传事件,包括已经初始化的尚未Complete或者Abort的分片上传事件。默认情况下,每次list操作最多返回1000个分片上传事件。以下代码展示如何简单列举分片上传事件:

String bucketName = "<your-bucket-name>";
ListMultipartUploadsRequest listMultipartUploadsRequest = new
        ListMultipartUploadsRequest(bucketName);
MultipartUploadListing multipartUploadListing = 
    	s3.listMultipartUploads(listMultipartUploadsRequest);
for (MultipartUpload multipartUpload : multipartUploadListing.getMultipartUploads()) {
    multipartUpload.getUploadId();
    multipartUpload.getInitiator();
    multipartUpload.getInitiated();
    multipartUpload.getKey();
}

如果list大于1000,则返回的结果中 isTruncated 为true,并返回 NextKeyMarker NextUploadIdMarker 作为下次读取的起点。如果没有一次性获取所有的分片上传事件,可以采用分页列举的方式。列举所有分片上传事件示例代码如下:

String bucketName = "<your-bucket-name>";
MultipartUploadListing multipartUploadListing;
ListMultipartUploadsRequest listMultipartUploadsRequest = new
        ListMultipartUploadsRequest(bucketName);
do {
    multipartUploadListing = s3.listMultipartUploads(listMultipartUploadsRequest);
    for (MultipartUpload multipartUpload : multipartUploadListing.getMultipartUploads()) {
        multipartUpload.getUploadId();
        multipartUpload.getInitiator();
    	multipartUpload.getInitiated();
        multipartUpload.getKey();
    }
    listMultipartUploadsRequest.setKeyMarker(multipartUploadListing.getNextKeyMarker());
    listMultipartUploadsRequest.setUploadIdMarker(multipartUploadListing.getNextUploadIdMarker());
} while (multipartUploadListing.isTruncated());