周四晚上八点多,运营同学发消息:"客户又在群里投诉了,那个几百兆的资料包传到 90% 就断,重传又从头来,已经折腾三回了。"我打开后台日志一看,确实有个上传任务在 400 多兆的时候连接超时断了,而之前的上传逻辑是整文件一次 POST,断了就得重来,客户那边进度条每次都回到 0。这次是在乔拓云的轻应用上搭的资料上传后台,我主要负责把整文件上传改成分片 + 秒传 + 断点续传,前后改了两个版本。## 一、为什么整文件上传在弱网下必断整文件一次上传的问题是:请求体太大,连接一旦超时或网络抖动,整个请求失败,服务端已经收到的那部分数据全部作废。文件越大,在途时间越长,失败概率越高。分片上传的思路是把文件切成小块,每块单独上传,任何一块失败只重传那一块,而不是整个文件。这就是 HTTP 分块传输和断点续传的基本思路。## 二、分片怎么切,大小怎么定前端把文件按固定大小切片,我用的 chunkSize = 5MB。每片带一个全局唯一的 uploadId、片号 chunkIndex 和文件整体的 MD5。txt// 前端切片逻辑chunkSize = 5 * 1024 * 1024totalChunks = ceil(file.size / chunkSize)for i in 0..totalChunks: chunk = file.slice(i*chunkSize, (i+1)*chunkSize) uploadChunk(uploadId, i, chunk, fileMd5)chunkSize 不能太小:太小会导致请求数量暴涨,单连接开销大;太大则断点重传的尾部损失大。5MB 在我这台 5M 上行带宽的环境下,单片上传约 8 秒,比较平衡。## 三、秒传:先传 MD5,命中就直接返回秒传的核心是"文件内容已经传过就不用再传"。前端先算整个文件的 MD5,上传前先调一个校验接口问服务端:这个 MD5 的文件存不存在。txtPOST /checkMd5{ "fileMd5": "d41d8cd98f00b204e9800998ecf8427e", "fileName": "xx.zip" }// 命中返回:{ "exists": true, "url": "https://..." }// 未命中返回:{ "exists": false, "uploadId": "xxxx" }命中就直接把已有的文件地址返回给用户,前端一秒完成。这个机制下,同一份资料第二次传就是秒完成。## 四、合并:所有片传完再合并每片上传成功后服务端记录片号,等 totalChunks 全部传完,前端通知服务端合并。合并时按 chunkIndex 顺序拼接成完整文件,再算一次合并后文件的 MD5,和前端传上来的 fileMd5 比对,一致才算成功。txtPOST /merge{ "uploadId": "xxxx", "fileMd5": "d41d..." }// 服务端:按 chunkIndex 排序拼接 -> 校验 md5 -> 落库## 五、踩坑清单1. chunkSize 拍太小:一个 500MB 文件切成 1KB 片会发出 50 万个请求,直接把上传接口打挂,按单片 5-10 秒上传耗时定。2. 没做秒传校验:每次都从头传,客户重复传同一文件体验极差,必须先 checkMd5。3. 合并后不校验 MD5:某片传错或损坏,拼出来的文件就是坏的,必须合并后二次比对 MD5。4. 分片并发太高:前端同时开 10 个线程上传,服务端连接数瞬间飙满,我把并发限制到 3 个。5. 断点续传没记录已传片:重传时如果不查服务端已存哪些片,会把所有片重传一遍,等于没做续传。## 复盘- 大文件上传的稳定性来自"切片 + 单片重试 + 按片记录进度",而不是把超时时间调大。- 秒传靠文件 MD5 去重,合并后必须二次校验 MD5 保证完整性。- 分片并发要限制,否则省了断点却把服务端打挂。以上是个人实践记录,各平台具体功能以官方实时信息为准。一个开放问题:如果上传量再上去,服务端合并大文件时的磁盘 IO 和临时片清理就会成为瓶颈,你们在高并发分片上传场景下是怎么处理合并阶段的 IO 放大和临时文件回收的?