2.3 大文件分片与断点续传:一小时会议 200MB

业务场景:会议室的 Wi-Fi 教做人

我们的会议室在楼道尽头,信号常年一格。第一次跑全流程的时候,一小时的产品周会录音传到 97% 断了线,刷新页面从头再来,又传了 80% 多掉线,第三次硬是等到有人开热点才传完。那天我就明白,在一个两百人的创业团队里推广工具,上传环节脆弱一点,后面转写得再好也没人用。

这节解决的就是大文件在弱网下的传输问题:会议录音到底有多大、为什么分片上传能扛住断线、断点续传靠什么记住进度、以及弱网场景我真实踩过的坑。整套存储底座的搭建,可以配合腾讯云五件套实战的存储章节一起理解。补一句题外话:这套上传站点的部署用了一台轻量服务器,配置过程在轻量建站实战里有完整记录,两本书的进度可以对照着看。

文件规模对照

先看数据,知道量级才知道方案怎么选。我们会议室用一支全向麦克风,录多通道高码率的原始格式:

会议时长 wav 近似体积 m4a 近似体积 上传策略
15 分钟站会 约 50MB 约 10MB 直传即可
30 分钟对口会 约 100MB 约 20MB 建议分片
1 小时例会 约 200MB 约 40MB 必须分片加续传
2 小时项目会 约 400MB 约 80MB 必须分片加续传
半天复盘会 约 800MB 约 160MB 建议先本地转码压缩

这张表是我们按 16bit PCM 双声道 wav、每分钟约 3.3MB 估出来的近似值。实际会因各家设备的采样率设置略有出入,但量级足够指导方案选择。表格想传递的信息只有一个:只要坚持原始质量优先,半小时以上的会议就必须直面百兆级传输,分片和续传不是可选项。

可以看出,只要用 wav,半小时以上就过百 MB,浏览器的单连接上传在弱网下几乎必然失败。我们的做法是统一录 wav 保质量,上传时用分片,代价用工程手段补回来。

分片上传原理

分片上传(multipart upload)是 COS 原生支持的能力,原理可以拆成三步。

第一步是初始化,前端先告诉 COS 我要传一个大文件,COS 返回一个上传会话编号。第二步是切片上传,文件在本地被切成固定大小的分片(我用 5MB,两百兆文件就是四十片),每一片独立上传,互不影响;重点是并发——浏览器可以同时开三到五个连接并行传,总时长几乎按并发数线性缩短。每个分片传完会带一个 MD5 校验值,COS 端计算不一致就拒绝这片,要求重传,保证数据在弱网下也不损坏。第三步是完成合并,所有分片都确认到位后,前端调一次完成接口,COS 把分片拼回完整对象,对象立即可读。

分片大小的选择有讲究。标准网络下大片效率高,一个 200MB 的文件如果每片 20MB 只要 10 个请求,但单片失败重传成本高;弱网下小片更坚韧,1MB 一片传坏了也就重传 1MB。最终我用了两档:默认 5MB,检测到网速低于阈值时自动切 1MB。并发数同理,开 3 个连接较保守,网络好的时候开到 5,超过 5 浏览器的连接管理反而成了瓶颈,速度不升反降。

这套参数也是被数据教育出来的。最初我用固定 10MB 加并发 2,自以为稳妥,结果弱网下单片重传的时间足够传五片 1MB。后来加了分片级别的耗时统计,看图调参:以平均单片成功耗时为目标,耗时越低分片越小,最终收敛到 5MB 和 1MB 两档。调参的心法是让数据说话,而不是凭感觉拍一个看起来合理的数。

这里有个容易被忽略的点:MD5 校验是分片级别的,只有逐片校验对了,合并才是安全的。所以重试逻辑要写在单个分片的粒度上,而不是整个文件级别。

再补一个选型层面的考量:为什么不直接压缩成低码率文件再传?因为预处理(2.4 节)发生在服务端,客户端压一次相当于把质量损失前置了,识别率要有波动都分不清是网络还是压坏音频的锅。把质量保住、用工程手段解决传输慢,是更安全的分工。

这套方案上线后我还留了个心眼,把上传耗时和成功率按会议室维度存了一份明细。一个月后看数据,同样是弱网,三楼会议室的失败率明显高,原来是那台无线路由器顶在天花板里被金属桥架挡着。工程方案再精巧,也代替不了去现场看一眼路由器——这是我后来逢人就讲的经验。

断点续传设计

断点续传的核心是进度持久化:上传过程中把已完成的分片编号记下来,断线之后从下一个分片继续,而不是重新切整个文件。

场景 本地记录什么 恢复流程
页面误刷新 localStorage 里的会话编号与分片位图 重新初始化页面后读取记录,向 COS 查询已上传分片,只传缺失的
Wi-Fi 断开重连 同上,标记断点时间 网络恢复后自动续传,暂停状态全程提示用户
换一台电脑继续 服务端记录的已完成分片列表 新设备向服务端查进度,直接从服务端记录的断点继续
分片永久失败 失败分片编号与重试次数 单片重试三次仍失败则提示切换网络,会话保留
会话过期 无需记录,直接丢弃 重新申请临时密钥,已上传分片在 COS 端仍然有效

第三行是让我松了口气的发现:分片上传的会话在 COS 端有有效期,只要会话还在,换设备也能查到已上传的分片,不用从头来。所以进度记录我做了双份,前端 localStorage 一份,服务端一份,前端记录丢了还能找服务端要。

服务端那份记录顺带解决了另一个管理诉求:谁传到哪了、传了多久、失败在哪一片,后端日志里一目了然,排查弱网问题终于有了数据,而不是靠用户口头描述。

快速估算:续传到底省多少

拿真实的数算一笔:一个 200MB 的录音,公司网络稳定时 3 分钟传完;弱网下单连接速度可能只有十分之一,要半个小时。如果中途断在 80% 的位置,没有续传就是从头再来 30 分钟,有续传则是剩下的 20%,几分钟收工。越是恶劣的网络环境,续传的收益倍数越大。这也是为什么我不接受把续传当锦上添花的功能——对经常出差、在客户现场、在咖啡馆开会的人来说,它决定了这个工具用还是不用。

再补一个边界情况:录音传到一半用户彻底离开,服务端记录里会留下一个永久未完成的会话。我们加了个兜底的清理任务,超过 24 小时没完成的断点记录直接归档,录音桶里对应的半成品分片也一并清掉,存储不积垃圾,列表里也不会出现永远传不完的幽灵文件。

顺带交代一个和浏览器有关的技术细节:上传过程中页面一旦刷新,localStorage 里的记录要赶紧补全最后一秒的进度,我们做成每完成一个分片就异步写一次,而不是用 unload 事件。unload 在移动端浏览器上不可靠,指望它往往就是丢失进度的开始。

最后说一句兼容性:这套方案的客户端前提是现代浏览器,IE 之类的老古董不在支持范围内,好在公司内部工具不用背历史包袱。移动端浏览器测试过主流的几款,分片和续传都能跑,只有个别旧版安卓 WebView 的并发数要降到 2,算是小遗憾。

踩坑:弱网三次失败之后

最惨的一次复盘会上,两小时的项目会传了一半,进度条卡在 60% 长达十分钟,用户以为死机关了页面,一夜白传。那天之后我深刻意识到:对于上传这种长耗时操作,进度条的诚实程度比速度本身更重要。卡住的时候要明确提示当前在重试哪一片,而不是假装一切正常。事后我做了三件事来杜绝。

第一件是把分片大小改成自适应。固定 5MB 在公司宽带下没问题,但在一格信号的环境里,单片传 30 秒还没响应,重试成本很高。改成按当前网速动态选档,测得到慢就切 1MB 的小片,单片失败成本低,重试也快。

这件事背后有个反直觉的点:很多人以为弱网该等好网络再传,实际上越拖文件越多,积压到最后还是要传。分片小化相当于把整个任务拆成无数个可以随手做掉的小动作,牵着弱网的鼻子走,而不是被它牵着走。

第二件是给整个上传加暂停和恢复按钮。弱网下与其和信号搏斗,不如让用户选个网络好的时候续传。会话在 COS 端保留了足够时间,晚上回家传完第二天一样能转写,用户预期一旦变成可中断可恢复,抱怨就消失了。

第三件是上传完成后的兜底校验。合并成功不等于文件没暗伤,我们在合并后比对一次对象大小与本地文件的 MD5,不一致就触发重新上传。这一个检查上线后抓到过两次静默损坏,避免了转写环节拿到坏文件报错半天查不到原因。

还有一个心态层面的经验:上传环节一定要给用户确定性。弱网是物理条件,抱怨没用,但成功率是工程指标。改造之后我们的上传完成率从八成出头提到接近满分,剩下的少数失败全是可恢复的网络问题。打好上传这一仗,后面转写和纪要的口碑才立得住。

最后回扣一句架构层面的取舍:为什么客户端能做这么多?因为分片、记录、续传这些状态本质上是用户设备上的本地状态,放在客户端最自然。服务端只维护需要跨设备共享的那一份进度。这个职责划分想清楚了,实现的时候就不会把所有进度数据都堆给服务端,也不会出现前端和服务端进度打架、各说各话的灵异现场。

延伸阅读