分片并发上传实现
先上流程图
文件上传步骤
1.文件分片处理:将文件按 5MB 大小切分为多个 Blob 分片,并发送文件信息至后端。
2.获取预签名 URL:后端为每个分片生成预签名上传 URL,并返回给前端,用于前端上传。
3.并发上传分片:前端控制最多同时 3 个上传任务,逐个发起 PUT 请求上传分片。上传成功则继续下一个分片,上传失败则自动重试,自动重试最多 3 次。
4.所有分片上传成功后,调用后端合并接口,完成文件上传。
一、文件切片处理
使用 File.slice()
将文件切分为多个 Blob
。
文件切片好处
1. 拆分为小片,避免因网络波动导致整体上传的失败,支持断点续传。
2. 并发上传多个分片,可以缩短整体上传时间。
3. 若上传失败仅需重传失败的分片,无需重复上传整个文件。
4. 可控制单次请求大小,避免超时或内存溢出。
为什么使用 Blob
而非 FileBuffer
用 Blob
可以有效降低内存占用
1. Blob
是惰性加载的:Blob
对象本身并不立即将文件内容加载到内存中。它更像是一个指向文件某部分的“引用”或“句柄”。只有当真正读取 Blob
内容时(例如通过 FileReader
或 Response
API),数据才会被加载。
2.ArrayBuffer
/Buffer
是内存驻留的:创建 ArrayBuffer
意味着立即将切片的数据复制到内存中,占用实际的内存空间。对于大文件,这会导致内存占用急剧上升,可能引发性能问题或内存溢出。
具体代码:
export const getFileChunk = async (file, chunkSize = 8 * 1024 * 1024) => {const chunks = await getFileChunkAsBlob(file, chunkSize)return chunks
}// 只分片不读取数据,返回 Blob
const getFileChunkAsBlob = (file, chunkSize) => {return new Promise(resolve => {const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSliceconst chunks = Math.ceil(file.size / chunkSize)const chunksMap = {}// 循环生成 Blob 分片for (let i = 0; i < chunks; i++) {const start = i * chunkSizeconst end = Math.min(start + chunkSize, file.size)chunksMap[i + 1] = blobSlice.call(file, start, end) // 返回 Blob}resolve(chunksMap)})
}
二、获取分片上传 URL(URL 经过预签名)
预签名 URL 是一个带有临时授权信息(如签名、过期时间、权限范围)的 URL,由后端使用云存储的访问密钥(Access Key / Secret Key)生成,允许前端通过该 URL 直接上传或下载。
核心目的:避免暴露长期密钥,安全地授权第三方临时访问受保护的资源。
预签名 URL好处
1. 精确控制操作类型(如只能上传或下载)
2. 控制URL过期时间
3. 控制只能访问某个特定文件
具体:调用后端接口,获取每个分片的上传地址和 uploadId。
【代码略,涉及业务逻辑,后期更新】
三、并发上传分片(带重试)
前端启动上传时,使用一个并发控制队列,控制最多同时发起 3 个分片上传请求。维护一个待上传任务列表,每次从队列中取出一个分片,通过预签名 URL 发起 PUT
请求上传。
上传成功
后端返回 200
,并携带 ETag
头。前端记录该分片的 partNumber
和 ETag
,更新上传进度,并继续发起下一个分片的上传。
上传失败
若请求因网络问题、超时等失败,则将该分片标记为“待重试”,并将其加入重试队列。
1. 每个分片最多重试 3 次。
2. 重试采用指数退避策略:第一次失败后等待 1 秒,第二次等待 2 秒,第三次等待 4 秒再发起。
3. 若 3 次重试均失败,则整个上传任务中断,提示用户“上传失败,请检查网络后重试”。
指数退避策略
在网络抖动或服务器短暂过载时,密集重试会加剧问题,甚至导致服务崩溃,指数退避让每次重试的等待时间成倍增长,不会在同一时间重试,分散请求压力。
具体:使用 并发控制 + 重试机制 上传分片:
【代码略,涉及业务逻辑,后期更新】
四、合并分片(Merge)
所有分片上传成功后,通知服务器合并
【代码略,涉及业务逻辑,后期更新】