OSS提供的分片上传(Multipart Upload)功能,将要上传的较大文件(Object)分成多个数据块(Part)来分别上传,上传完成后再调用CompleteMultipartUpload接口将这些Part组合成一个Object来达到断点续传的效果。
分片上传流程
分片上传(Multipart Upload)分为以下三个步骤:
- 初始化一个分片上传事件。
调用InitiateMultipartUploadRequest方法返回OSS创建的全局唯一的uploadId。
- 上传分片。
调用UploadPartRequest方法上传分片数据。
说明- 对于同一个UploadId,分片号(PartNumber)标识了该分片在整个文件内的相对位置。如果使用同一个分片号上传了新的数据,那么OSS上这个分片已有的数据将会被覆盖。
- OSS将收到的分片数据的MD5值放在ETag头内返回给用户。
- OSS计算上传数据的MD5值,并与SDK计算的MD5值比较,如果不一致则返回InvalidDigest错误码。
- 完成分片上传。
所有分片上传完成后,调用CompleteMultipartUploadRequest方法将所有分片合并成完整的文件。
分片上传的完整代码请参见GitHub。
分片上传完整示例
以下通过一个完整的示例对分片上传的流程进行逐步解析:
using Aliyun.OSS; var endpoint = "<yourEndpoint>"; var accessKeyId = "<yourAccessKeyId>"; var accessKeySecret = "<yourAccessKeySecret>"; var bucketName = "<yourBucketName>"; var objectName = "<yourObjectName>"; var localFilename = "<yourLocalFilename>"; // 创建OssClient实例。 var client = new OssClient(endpoint, accessKeyId, accessKeySecret); // 初始化分片上传。 var uploadId = ""; try { // 定义上传的文件及所属存储空间的名称。您可以在InitiateMultipartUploadRequest中设置ObjectMeta,但不必指定其中的ContentLength。 var request = new InitiateMultipartUploadRequest(bucketName, objectName); var result = client.InitiateMultipartUpload(request); uploadId = result.UploadId; // 打印UploadId。 Console.WriteLine("Init multi part upload succeeded"); Console.WriteLine("Upload Id:{0}", result.UploadId); } catch (Exception ex) { Console.WriteLine("Init multi part upload failed, {0}", ex.Message); } // 计算分片总数。 var partSize = 100 * 1024; var fi = new FileInfo(localFilename); var fileSize = fi.Length; var partCount = fileSize / partSize; if (fileSize % partSize != 0) { partCount++; } // 开始分片上传。PartETags是保存PartETag的列表,OSS收到用户提交的分片列表后,会逐一验证每个分片数据的有效性。当所有的数据分片通过验证后,OSS会将这些分片组合成一个完整的文件。 var partETags = new List<PartETag>(); try { using (var fs = File.Open(localFilename, FileMode.Open)) { for (var i = 0; i < partCount; i++) { var skipBytes = (long)partSize * i; // 定位到本次上传的起始位置。 fs.Seek(skipBytes, 0); // 计算本次上传的分片大小,最后一片为剩余的数据大小。 var size = (partSize < fileSize - skipBytes) ? partSize : (fileSize - skipBytes); var request = new UploadPartRequest(bucketName, objectName, uploadId) { InputStream = fs, PartSize = size, PartNumber = i + 1 }; // 调用UploadPart接口执行上传功能,返回结果中包含了这个数据片的ETag值。 var result = client.UploadPart(request); partETags.Add(result.PartETag); Console.WriteLine("finish {0}/{1}", partETags.Count, partCount); } Console.WriteLine("Put multi part upload succeeded"); } } catch (Exception ex) { Console.WriteLine("Put multi part upload failed, {0}", ex.Message); } // 完成分片上传。 try { var completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, objectName, uploadId); foreach (var partETag in partETags) { completeMultipartUploadRequest.PartETags.Add(partETag); } var result = client.CompleteMultipartUpload(completeMultipartUploadRequest); Console.WriteLine("complete multi part succeeded"); } catch (Exception ex) { Console.WriteLine("complete multi part failed, {0}", ex.Message); }
取消分片上传事件
您可以调用client.AbortMultipartUpload方法来取消分片上传事件。当一个分片上传事件被取消后,无法再使用这个uploadId做任何操作,已经上传的分片数据会被删除。
取消分片上传事件的完整代码请参见GitHub。
以下代码用于取消分片上传事件:
using Aliyun.OSS; var endpoint = "<yourEndpoint>"; var accessKeyId = "<yourAccessKeyId>"; var accessKeySecret = "<yourAccessKeySecret>"; var bucketName = "<yourBucketName>"; var objectName = "<yourObjectName>"; var uploadId = ""; // 创建OssClient实例。 var client = new OssClient(endpoint, accessKeyId, accessKeySecret); // 初始化分片上传。 try { var request = new InitiateMultipartUploadRequest(bucketName, objectName); var result = client.InitiateMultipartUpload(request); uploadId = result.UploadId; // 打印UploadId。 Console.WriteLine("Init multi part upload succeeded"); Console.WriteLine("Upload Id:{0}", result.UploadId); } catch (Exception ex) { Console.WriteLine("Init multi part upload failed, {0}", ex.Message); } // 取消分片上传。 try { var request = new AbortMultipartUploadRequest(bucketName, objectName, uploadId); client.AbortMultipartUpload(request); Console.WriteLine("Abort multi part succeeded, {0}", uploadId); } catch (Exception ex) { Console.WriteLine("Abort multi part failed, {0}", ex.Message); }
列举已上传的分片
列举已上传分片的完整代码请参见GitHub。
以下代码用于列举已上传的分片:
using Aliyun.OSS; var endpoint = "<yourEndpoint>"; var accessKeyId = "<yourAccessKeyId>"; var accessKeySecret = "<yourAccessKeySecret>"; var bucketName = "<yourBucketName>"; var objectName = "<yourObjectName>"; var uploadId = "<yourUploadId>"; // 创建OssClient实例。 var client = new OssClient(endpoint, accessKeyId, accessKeySecret); try { PartListing listPartsResult = null; var nextMarker = 0; do { var listPartsRequest = new ListPartsRequest(bucketName, objectName, uploadId) { PartNumberMarker = nextMarker, }; // 列举已上传的分片。 listPartsResult = client.ListParts(listPartsRequest); Console.WriteLine("List parts succeeded"); // 遍历所有分片。 var parts = listPartsResult.Parts; foreach (var part in parts) { Console.WriteLine("partNumber: {0}, ETag: {1}, Size: {2}", part.PartNumber, part.ETag, part.Size); } nextMarker = listPartsResult.NextPartNumberMarker; } while (listPartsResult.IsTruncated); } catch (Exception ex) { Console.WriteLine("List parts failed, {0}", ex.Message); }
列举分片上传事件
调用ossClient.listMultipartUploads方法列举出所有执行中的分片上传事件,即已初始化但尚未完成或已取消的分片上传事件。可设置的参数如下:
参数 | 作用 | 设置方法 |
---|---|---|
prefix | 限定返回的文件名称必须以指定的prefix作为前缀。注意使用prefix查询时,返回的文件名称中仍会包含prefix。 | ListMultipartUploadsRequest.setPrefix(String prefix) |
delimiter | 用于对文件名称进行分组的一个字符。所有名称包含指定的前缀且第一次出现delimiter字符之间的文件作为一组元素。 | ListMultipartUploadsRequest.setDelimiter(String delimiter) |
maxUploads | 限定此次返回分片上传事件的最大数目,默认值和最大值均为1000。 | ListMultipartUploadsRequest.setMaxUploads(Integer maxUploads) |
keyMarker | 所有文件名称的字母序大于keyMarker参数值的分片上传事件。可以与uploadIdMarker参数一同使用来指定返回结果的起始位置。 | ListMultipartUploadsRequest.setKeyMarker(String keyMarker) |
uploadIdMarker | 与keyMarker参数一同使用来指定返回结果的起始位置。 如果未设置keyMarker参数,则此参数无效。如果设置了keyMarker参数,则查询结果中包含:
|
ListMultipartUploadsRequest.setUploadIdMarker(String uploadIdMarker) |
列举分片上传事件的完整代码请参见GitHub。
以下代码用于列举分片上传事件:
using Aliyun.OSS; var endpoint = "<yourEndpoint>"; var accessKeyId = "<yourAccessKeyId>"; var accessKeySecret = "<yourAccessKeySecret>"; var bucketName = "<yourBucketName>"; // 创建OssClient实例。 var client = new OssClient(endpoint, accessKeyId, accessKeySecret); try { MultipartUploadListing multipartUploadListing = null; var nextMarker = string.Empty; do { // 列举分片上传事件。 var request = new ListMultipartUploadsRequest(bucketName) { KeyMarker = nextMarker, }; multipartUploadListing = client.ListMultipartUploads(request); Console.WriteLine("List multi part succeeded"); // 列举各个分片上传事件的信息。 foreach (var mu in multipartUploadListing.MultipartUploads) { Console.WriteLine("Key: {0}, UploadId: {1}", mu.Key, mu.UploadId); } // 返回结果中isTruncated为false,nextKeyMarker和nextUploadIdMarker作为下次读取的起点。 nextMarker = multipartUploadListing.NextKeyMarker; } while (multipartUploadListing.IsTruncated); } catch (Exception ex) { Console.WriteLine("List multi part uploads failed, {0}", ex.Message); }
指定前缀和最大返回条数
以下代码通过指定前缀和最大返回条数来进行分片上传:
using Aliyun.OSS; var endpoint = "<yourEndpoint>"; var accessKeyId = "<yourAccessKeyId>"; var accessKeySecret = "<yourAccessKeySecret>"; var bucketName = "<yourBucketName>"; // 定义前缀。 var prefix = "<yourObjectPrefix>"; // 定义最大返回条数为100。 var maxUploads = 100; // 创建OssClient实例。 var client = new OssClient(endpoint, accessKeyId, accessKeySecret); try { MultipartUploadListing multipartUploadListing = null; var nextMarker = string.Empty; do { // 列举分片上传事件。默认列举1000个分片。 var request = new ListMultipartUploadsRequest(bucketName) { KeyMarker = nextMarker, // 指定前缀。 Prefix = prefix, // 指定最大返回条数。 MaxUploads = maxUploads, }; multipartUploadListing = client.ListMultipartUploads(request); Console.WriteLine("List multi part succeeded"); // 列举各个分片上传事件的信息。 foreach (var mu in multipartUploadListing.MultipartUploads) { Console.WriteLine("Key: {0}, UploadId: {1}", mu.Key, mu.UploadId); } nextMarker = multipartUploadListing.NextKeyMarker; } while (multipartUploadListing.IsTruncated); } catch (Exception ex) { Console.WriteLine("List multi part uploads failed, {0}", ex.Message); }