【文件服务】断点续传(上传)

521 阅读4分钟

「这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战

一、前言

分块上传主要分三步:

  1. 初始化上传
  2. 上传分块
  3. 通知上传完成

断点续传,会另提供一个接口:查询分块列表。

使用时机:完成初始化上传后。

  • 查询出此次需要上传分块序号
  • 客户端根据需要上传对应分块

时序图如下:

断点续传-断点续传.png

(1)接口方案一

接口定义如下:

GET /multiUpload/blocks
Parameter: uploadId=6989130131105267718
  
Response:
{
    "code": 0,
    "msg": "OK",
    "data": {
        "nextBlockSeq": 8
    }
}

输入参数说明:

参数说明是否允许为空描述
uploadIdstring上传Id

返回参数说明:

参数类型是否允许为空描述
nextBlockSeqint下个需要上传的分块,-1 表示没有需要上传分块

此接口定义缺点:不支持并行上传,可能出现重复上传。

# 举个栗子
待上传 0, 1, 2, 3, 4, 5, 6, 8

线程1 上传:0, 1, 2
线程2 上传:3, 4
并发上传完成:0, 1, 3, 4


# 此时查询列表接口
nextBlockSeq = 2

# 那么 3,4 就需要重新上传

此接口定义优点:简单,适用于目前上传。


(2)接口方案二

此方案弥补方案一的不足:支持并行。

举个栗子:飞书

  • 客户端:上传所有所需分块
  • 服务端:查询已上传的所有分块,与客户端的分块做差
POST https://percent.larksuite.com/space/api/box/upload/blocks/
{
	"upload_id":"7064755580988030981",
    "mount_point":"explorer"
    "blocks":[
        {
            "hash":"xn22HCAKOMRg7Z2cXw97lKH03EwPm780WS+3l8OxTww=",
            "seq":0,
            "size":4194304,
            "checksum":"1974019785"
        },
        // ... ...
        {
            "hash":"uUaU/oaQ0ADEn8LW/vGr6wZpkaJHzyQDW4PkdmhQeuY=",
            "seq":18,
            "size":322515,
            "checksum":"1921767174"
        }
    ]
}

# 返回
{
    "code":0,
    "message":"Success",
    "data":{
        "needed_upload_blocks":[
            {
                "seq":0,
                "size":4194304,
                "checksum":"1974019785",
                "hash":"xn22HCAKOMRg7Z2cXw97lKH03EwPm780WS+3l8OxTww="
            },
            // ... ...
            {
                "seq":18,
                "size":322515,
                "checksum":"1921767174",
                "hash":"uUaU/oaQ0ADEn8LW/vGr6wZpkaJHzyQDW4PkdmhQeuY="
            }
        ]
    }
}

(3)查询接口异常处理

查询分块列表,失败情况下,处理:

  1. 服务异常:停止上传

用户点击感叹号,再次重头上传

lQDPDhspo1ElhkjM8M0EOLCvXhAqoU7IzAIU5JCMANEA_1080_240.jpg

  1. uploadId 不存在:重新上传

直接重新上传。 用户界面显示 “菊花” 旋转。

lQDPDhspo1ElhNvNAQjNBDiwN-q8a8IAtPsCFOSQAcDRAA_1080_264.jpg

  1. 网络中断:停止上传

列表出现感叹号,并弹出 toast:网络不可用

lQDPDhspo1G-GuXNA3XNBCqw9ZD_ja-UIhwCFOSQ8oDRAA_1066_885.jpg



二、实现

技术要点:

  • redis:存储上传文件信息
  • ceph:存储层

实现步骤:

  1. 先从 redis 中查询对应 uploadId 的信息
  2. 再查找 ceph 中已上传的分块信息

这里给出 ceph 操作:

/**
 * 列举分块上传的分片
 *
 * Tips: 根据 IsTruncated 来判断是否是最后一个列表
 *       然后查处下一个需要上传的分块
 *
 * @param bucketName 桶名
 * @param objectKey 对象key
 * @param uploadId 上传Id
 * @return 分片信息
*/
public PartListing listMultipartes(String bucketName, String objectKey, String uploadId) {

    ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, 
                                                             objectKey, uploadId);

    return ossClient.listParts(listPartsRequest);
}

构建 ListPartsRequest 请求参数:

名称类型示例值描述
uploadIdstring2~z3rSWDp6X6fKYgHdeLD_blyWBwhpBrdMultipartUpload 事件的ID
max-partsint1000规定在OSS响应中的最大Part数目。默认值:1,000最大值:1,000
part-number-markerint100指定List的起始位置,只有Part Number数目大于该参数的Part会被列出。默认值:无
encoding-typestringurl指定对返回的内容进行编码,指定编码的类型。Key使用UTF-8字符,但xml 1.0标准不支持解析一些控制字符,比如ascii值从0到10的字符。对于Key中包含xml 1.0标准不支持的控制字符,可以通过指定Encoding-type对返回的Key进行编码。默认值:无可选值:url

响应头:

名称类型示例值描述
ListPartsResult容器不涉及分片信息。
Bucket字符串multipart_uploadBucket名称。
EncodingType字符串url指明对返回结果进行编码使用的类型。
Key字符串multipart.dataObject名称。
UploadId字符串2~z3rSWDp6X6fKYgHdeLD_blyWUpload事件ID。
PartNumberMarker整数10本次List结果的Part Number起始位置
NextPartNumberMarker整数5用于标明接下来请求的PartNumberMarker值。
MaxParts整数1000返回请求中最大的Part数目。
IsTruncated枚举字符串false标明本次返回的ListParts结果列表是否被截断。“true”表示本次没有返回全部结果;“false”表示本次已经返回了全部结果。
Part容器不涉及保存Part信息的容器。子节点:PartNumber,LastModified, ETag, Size
PartNumber整数1标示Part的数字。
LastModified日期2012-02-23T07:01:34.000ZPart上传的时间。
ETag字符串3349DC700140D7F86A0784842780已上传Part内容的ETag。
Size整数6291456已上传Part大小。