Object的分块上传

除了通过putObject()方法上传文件到BOS以外,BOS还提供了另外一种上传模式:分块上传(Multipart Upload)。用户可以在如下的应用场景内(但不仅限于此),使用分块上传模式,如:

  • 需要支持断点上传。
  • 上传超过5GB大小的文件。
  • 网络条件较差,和BOS的服务器之间的连接经常断开。
  • 需要流式地上传文件。
  • 上传文件之前,无法确定上传文件的大小。

分块完成Multipart Upload

假设有一个文件,本地路径为 d:\\sample.txt ,由于文件比较大,将其分块传输到BOS中。

  • 基本流程

    1. 创建一个BOSClient类的实例。
    2. 调用BosClient.InitiateMultipartUpload()方法。在调用该方法时您需要提供Bucket名称和Object名称,该方法会返回一个UploadId,在后面的步骤中需要用到该Id。
    3. 多次调用调用BosClient.UploadPart方法将文件分块多次上传。调用该方法时您需要提供Bucket名称,Object名称,UploadId,分块序号,分块大小,分块内容等参数,每次调用该方法都会返回本次分块的序号和ETag,在后面的步骤中会用到。
    4. 调用BosClient.CompleteMultipartUpload()方法完成本次分块上传。调用该方法您需要提供Bucket名称,Object名称,UploadId以及每个分块的序号和ETag。
    5. 上传过程中可以通过BosClient.ListParts()方法获取指定UploadId中已经上传的所有块;还可以通过BosClient.ListMultipartUploads()方法获取指定Bucket中所有未完成的UploadId。

初始化Multipart Upload

使用 InitiateMultipartUpload 方法来初始化一个分块上传事件:

  • 示例代码

    // 开始Multipart Upload
    InitiateMultipartUploadRequest initiateMultipartUploadRequest =
           new InitiateMultipartUploadRequest() {BucketName = bucketName, Key = objectKey};
    InitiateMultipartUploadResponse initiateMultipartUploadResponse =
           client.InitiateMultipartUpload(initiateMultipartUploadRequest);
    
    // 打印UploadId
    Console.WriteLine("UploadId: " + initiateMultipartUploadResponse.UploadId);
    

    说明:initiateMultipartUpload 的返回结果中含有 UploadId ,它是区分分块上传事件的唯一标识,在后面的操作中,我们将用到它。

  • 完整示例

    请参考完整示例

上传分块

把文件分块上传。

  • 示例代码

    // 设置每块为 5Mb
    long partSize = 1024 * 1024 * 5L;
    
    FileInfo partFile = new FileInfo("my file");
    
    // 计算分块数目
    int partCount = (int) (partFile.Length / partSize);
    if (partFile.Length % partSize != 0)
    {
     partCount++;
    }
    
    // 新建一个List保存每个分块上传后的ETag和PartNumber
    List<PartETag> partETags = new List<PartETag>();
    
    for (int i = 0; i < partCount; i++)
    {
     // 获取文件流
     Stream stream = partFile.OpenRead();
    
     // 跳到每个分块的开头
     long skipBytes = partSize * i;
     stream.Seek(skipBytes, SeekOrigin.Begin);
    
     // 计算每个分块的大小
     long size = Math.Min(partSize, partFile.Length - skipBytes);
    
     // 创建UploadPartRequest,上传分块
     uploadPartRequest uploadPartRequest = new UploadPartRequest();
     uploadPartRequest.BucketName = bucketName;
     uploadPartRequest.Key = objectKey;
     uploadPartRequest.UploadId = initiateMultipartUploadResponse.UploadId;
     uploadPartRequest.InputStream = stream;
     uploadPartRequest.PartSize = size;
     uploadPartRequest.PartNumber = i + 1;
     UploadPartResponse uploadPartResponse = client.UploadPart(uploadPartRequest);
    
     // 将返回的PartETag保存到List中。
     partETags.Add(new PartETag()
     {
        ETag = uploadPartResponse.ETag,
        PartNumber = uploadPartResponse.PartNumber
      });
    
     // 关闭文件
     stream.Close();
     }
    

    注意:

    上面代码的核心是调用 UploadPart 方法来上传每一个分块,但是要注意以下几点:

    • 分块上传方法要求除最后一个Part以外,其他的Part大小都要大于等于5MB。但是Upload Part接口并不会检查上传Part的大小;只有当Complete Multipart Upload的时候才会校验。
    • 为了保证数据在网络传输过程中不出现错误,建议您在UploadPart后,使用每个分块BOS返回的Content-MD5值分别验证已上传分块数据的正确性。当所有分块数据合成一个Object后,不再含MD5值。
    • Part号码的范围是1~10000。如果超出这个范围,BOS将返回InvalidArgument的错误码。
    • 每次上传Part时都要把流定位到此次上传块开头所对应的位置。
    • 每次上传Part之后,BOS的返回结果会包含一个 PartETag 对象,它是上传块的ETag与块编号(PartNumber)的组合,在后续完成分块上传的步骤中会用到它,因此需要将其保存起来。一般来讲这些 PartETag 对象将被保存到List中。
  • 完整示例

    请参考完整示例

完成分块上传

  • 示例代码

    CompleteMultipartUploadRequest completeMultipartUploadRequest =
    new CompleteMultipartUploadRequest()
    {
     BucketName = bucketName,
     Key = objectKey,
     UploadId = initiateMultipartUploadResponse.UploadId,
     PartETags = partETags
    };
    
    // 完成分块上传
    CompleteMultipartUploadResponse completeMultipartUploadResponse =
    client.CompleteMultipartUpload(completeMultipartUploadRequest);
    
    // 打印Object的ETag
    Console.WriteLine(completeMultipartUploadResponse.ETag);
    

    说明:上面代码中的 partETags 是第二部中保存的partETag的列表,BOS收到用户提交的Part列表后,会逐一验证每个数据Part的有效性。当所有的数据Part验证通过后,BOS将把这些数据part组合成一个完整的Object。

取消分块上传事件

用户可以使用abortMultipartUpload方法取消分块上传。

  • 示例代码

    AbortMultipartUploadRequest abortMultipartUploadRequest = new AbortMultipartUploadRequest()
    {
      BucketName = bucketName,
      Key = objectKey,
      UploadId = initiateMultipartUploadResponse.UploadId,
    };
    
    // 取消分块上传
    client.AbortMultipartUpload(abortMultipartUploadRequest);
    

获取未完成的分块上传事件

用户可以使用 ListMultipartUploads 方法获取Bucket内未完成的分块上传事件。

  • 示例代码

    ListMultipartUploadsRequest listMultipartUploadsRequest =
       new ListMultipartUploadsRequest() {BucketName = bucketName};
    
    // 获取Bucket内所有上传事件
    ListMultipartUploadsResponse listMultipartUploadsResponse =
       client.ListMultipartUploads(listMultipartUploadsRequest);
    
    // 遍历所有上传事件
    foreach (MultipartUploadSummary multipartUpload in listMultipartUploadsResponse.Uploads)
    {
     Console.WriteLine("Key: " + multipartUpload.Key + " UploadId: " + multipartUpload.UploadId);
    }
    

    注意:

    • 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个,并且返回结果中IsTruncated的值为True,同时返回NextKeyMarker作为下次读取的起点。
    • 若想获取更多分块上传事件,可以使用KeyMarker参数分次读取。
  • 完整示例

    请参考完整示例

获取所有已上传的块信息

用户可以使用 ListParts 方法获取某个上传事件中所有已上传的块。

  • 示例代码

    ListPartsRequest listPartsRequest = new ListPartsRequest()
    {
      BucketName = bucketName,
      Key = objectKey,
      UploadId = initiateMultipartUploadResponse.UploadId,
    };
    
    // 获取上传的所有Part信息
    ListPartsResponse listPartsResponse = client.ListParts(listPartsRequest);
    
    // 遍历所有Part
    foreach (PartSummary part in listPartsResponse.Parts)
    {
       Console.WriteLine("PartNumber: " + part.PartNumber + " ETag: " + part.ETag);
    }
    

    注意:

    • 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个part,并且返回结果中IsTruncated的值为True,同时返回NextPartNumberMarker作为下次读取的起点。
    • 若想获取更多已上传的分块信息,可以使用PartNumberMarker参数分次读取。
  • 完整示例

    请参考完整示例

完整示例

下面示例代码演示了分块上传的完整过程:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using BaiduBce;
using BaiduBce.Auth;
using BaiduBce.Services.Bos;
using BaiduBce.Services.Bos.Model;

namespace DotnetSample
{
    internal class MultiUploadSample
    {
        private static void Main(string[] args)
        {
            BosClient client = GenerateBosClient();
            const string bucketName = <BucketName>;    //Bucket名称

            // 初始化:创建示例Bucket
            client.CreateBucket(bucketName);
            string objectKey = <ObjectKey>;

            // 开始Multipart Upload
            InitiateMultipartUploadRequest initiateMultipartUploadRequest =
                new InitiateMultipartUploadRequest() { BucketName = bucketName, Key = objectKey };
            InitiateMultipartUploadResponse initiateMultipartUploadResponse =
                client.InitiateMultipartUpload(initiateMultipartUploadRequest);

            // 获取Bucket内的Multipart Upload
            ListMultipartUploadsRequest listMultipartUploadsRequest =
                new ListMultipartUploadsRequest() { BucketName = bucketName };       
            ListMultipartUploadsResponse listMultipartUploadsResponse =
                client.ListMultipartUploads(listMultipartUploadsRequest);
            foreach (MultipartUploadSummary multipartUpload in listMultipartUploadsResponse.Uploads)
            {
                Console.WriteLine("Key: " + multipartUpload.Key + " UploadId: " + multipartUpload.UploadId);
            }

            // 分块上传,首先设置每块为 5Mb
            long partSize = 1024 * 1024 * 5L;
            FileInfo partFile = new FileInfo("d:\\lzb\\sample");

            // 计算分块数目
            int partCount = (int)(partFile.Length / partSize);
            if (partFile.Length % partSize != 0)
            {
                partCount++;
            }

            // 新建一个List保存每个分块上传后的ETag和PartNumber
            List<PartETag> partETags = new List<PartETag>();
            for (int i = 0; i < partCount; i++)
            {
                // 获取文件流
                Stream stream = partFile.OpenRead();

                // 跳到每个分块的开头
                long skipBytes = partSize * i;
                stream.Seek(skipBytes, SeekOrigin.Begin);

                // 计算每个分块的大小
                long size = Math.Min(partSize, partFile.Length - skipBytes);

                // 创建UploadPartRequest,上传分块
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.BucketName = bucketName;
                uploadPartRequest.Key = objectKey;
                uploadPartRequest.UploadId = initiateMultipartUploadResponse.UploadId;
                uploadPartRequest.InputStream = stream;
                uploadPartRequest.PartSize = size;
                uploadPartRequest.PartNumber = i + 1;
                UploadPartResponse uploadPartResponse = client.UploadPart(uploadPartRequest);

                // 将返回的PartETag保存到List中。
                partETags.Add(new PartETag()
                {
                    ETag = uploadPartResponse.ETag,
                    PartNumber = uploadPartResponse.PartNumber
                });

                // 关闭文件
                stream.Close();
            }

            // 获取UploadId的所有Upload Part
            ListPartsRequest listPartsRequest = new ListPartsRequest()
            {
                BucketName = bucketName,
                Key = objectKey,
                UploadId = initiateMultipartUploadResponse.UploadId,
            };

            // 获取上传的所有Part信息
            ListPartsResponse listPartsResponse = client.ListParts(listPartsRequest);

            // 遍历所有Part
            foreach (PartSummary part in listPartsResponse.Parts)
            {
                Console.WriteLine("PartNumber: " + part.PartNumber + " ETag: " + part.ETag);
            }

            // 完成分块上传
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest()
                {
                    BucketName = bucketName,
                    Key = objectKey,
                    UploadId = initiateMultipartUploadResponse.UploadId,
                    PartETags = partETags
                };
            CompleteMultipartUploadResponse completeMultipartUploadResponse =
                client.CompleteMultipartUpload(completeMultipartUploadRequest);
            Console.WriteLine(completeMultipartUploadResponse.ETag);                  
            Console.ReadKey();
        }

        private static BosClient GenerateBosClient()
        {
            const string accessKeyId = <AccessKeyID>; // 您的Access Key ID
            const string secretAccessKey = <SecretAccessKey>; // 您的Secret Access Key
            const string endpoint = <EndPoint>; // 指定BOS服务域名

            // 初始化一个BosClient
            BceClientConfiguration config = new BceClientConfiguration();
            config.Credentials = new DefaultBceCredentials(accessKeyId, secretAccessKey);
            config.Endpoint = endpoint;

            return new BosClient(config);
        }
    }
}