「这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战」
一、前言
分块上传主要分三步:
- 初始化上传
- 上传分块
- 通知上传完成
断点续传,会另提供一个接口:查询分块列表。
使用时机:完成初始化上传后。
- 查询出此次需要上传分块序号
- 客户端根据需要上传对应分块
时序图如下:
(1)接口方案一
接口定义如下:
GET /multiUpload/blocks
Parameter: uploadId=6989130131105267718
Response:
{
"code": 0,
"msg": "OK",
"data": {
"nextBlockSeq": 8
}
}
输入参数说明:
| 参数 | 说明 | 是否允许为空 | 描述 |
|---|---|---|---|
uploadId | string | 否 | 上传Id |
返回参数说明:
| 参数 | 类型 | 是否允许为空 | 描述 |
|---|---|---|---|
nextBlockSeq | int | 是 | 下个需要上传的分块,-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)查询接口异常处理
查询分块列表,失败情况下,处理:
- 服务异常:停止上传
用户点击感叹号,再次重头上传
uploadId不存在:重新上传
直接重新上传。 用户界面显示 “菊花” 旋转。
- 网络中断:停止上传
列表出现感叹号,并弹出
toast:网络不可用
二、实现
技术要点:
redis:存储上传文件信息ceph:存储层
实现步骤:
- 先从
redis中查询对应uploadId的信息 - 再查找
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 请求参数:
| 名称 | 类型 | 示例值 | 描述 |
|---|---|---|---|
| uploadId | string | 2~z3rSWDp6X6fKYgHdeLD_blyWBwhpBrd | MultipartUpload 事件的ID |
| max-parts | int | 1000 | 规定在OSS响应中的最大Part数目。默认值:1,000最大值:1,000 |
| part-number-marker | int | 100 | 指定List的起始位置,只有Part Number数目大于该参数的Part会被列出。默认值:无 |
| encoding-type | string | url | 指定对返回的内容进行编码,指定编码的类型。Key使用UTF-8字符,但xml 1.0标准不支持解析一些控制字符,比如ascii值从0到10的字符。对于Key中包含xml 1.0标准不支持的控制字符,可以通过指定Encoding-type对返回的Key进行编码。默认值:无可选值:url |
响应头:
| 名称 | 类型 | 示例值 | 描述 |
|---|---|---|---|
| ListPartsResult | 容器 | 不涉及 | 分片信息。 |
| Bucket | 字符串 | multipart_upload | Bucket名称。 |
| EncodingType | 字符串 | url | 指明对返回结果进行编码使用的类型。 |
| Key | 字符串 | multipart.data | Object名称。 |
| UploadId | 字符串 | 2~z3rSWDp6X6fKYgHdeLD_blyW | Upload事件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.000Z | Part上传的时间。 |
| ETag | 字符串 | 3349DC700140D7F86A0784842780 | 已上传Part内容的ETag。 |
| Size | 整数 | 6291456 | 已上传Part大小。 |