| 场景 | 方案 | 理由 |
|---|---|---|
| 个人项目 / 内部工具(<200MB) | 整文件直传 | 分片是过度设计,调大 Nginx client_max_body_size 即可 |
| 公网产品,可用云 | 云存储 Multipart + 预签名直传 | 分片/续传/合并全是原生能力,流量不过自己服务器 |
| 私有化部署,不能上云 | 自建分片(成熟库 + 3 并发 + 8MB 片) | 精力花在 hash 性能与垃圾清理,不是协议本身 |
① 预上传:客户端算 hash → 服务端查「这文件在吗」
② 秒传 :命中 → 直接成功,0 字节传输
③ 续传 :未命中 → 服务端返回已有分片 → 只补缺失
④ 上传 :切片并发上传(带序号+单片hash)
⑤ 合并 :全部到齐 → 磁盘顺序合并 → 校验总 hash
这套流程的正确性没有争议。争议在于每一步的参数怎么定、坑在哪——下面全部用数据说话。
测试环境:1GB 文件,分片 1-32MB × 并发 1-10,共 28 组(本机基准测试,网络模型推演见第二张图)。

三个反直觉的发现:
1 并发 → 3 并发:吞吐 提升 164%;3 → 6:再提升 75%;6 → 10:只剩 8%,而客户端内存占用翻倍、移动端耗电显著增加。真实网络下(TCP 拥塞窗口共享用户带宽),6 以上并发还会挤占页面其他请求,导致 UI 卡死。结论:3-4 并发是甜点。

1% 的丢片率下,32MB 分片比 8MB 分片总时间多 30% 以上——因为每片失败要整片重传。而分片小于 2MB 时,RTT 往返占比过高,吞吐同样下滑。结论:公网 4-8MB 起步,弱网环境(移动端)降到 1-2MB,内网可到 16-32MB。
工程上最优雅的做法:首片用 4MB 探路,根据实际吞吐动态调整(吞吐 < 2MB/s → 降到 1-2MB;> 20MB/s → 升到 16MB)。成熟上传库(如 uppy、tusd)已内置类似逻辑。
秒传依赖「客户端算文件 hash」,但全量 hash 是个大坑:
| 方案 | 耗时(512MB 文件实测*) | 误判风险 |
|---|---|---|
| 全量 SHA-256(主线程) | ~0.2s(M 系芯片)但 UI 冻结;旧机器 2-5s+ | 无 |
| 全量 SHA-256(Web Worker) | 同上,但不卡 UI | 无 |
| 抽样 hash(首尾+间隔取 128KB) | <1ms | 极低(配合 size+mtime,服务端可二次全量校验) |
* Apple Silicon 实测;Intel/旧 Android 会慢 5-15 倍。工程组合拳:抽样 hash 做前端预判(快)+ 服务端落盘后全量校验(准),两层配合既快又稳。
「服务端合并分片」最大的坑:把所有分片读进内存再拼接——10 个用户各传 2GB 就 OOM。正确做法按优先级:
append 到目标文件(磁盘顺序写,内存占用恒定)用户传了 80% 关页面走人,这些 .part 文件谁清理?必须有定时任务:上传会话 24-72 小时未完成即删除分片。没做这个的系统,磁盘迟早被吃满。
「查询已有分片」接口必须以上传会话 ID + 文件 hash 为键,且并发查询不能互踩。两个标签页同时传同一文件是真实场景,不是抬杠。
文件 < 200MB?
├─ 是 → 整文件直传,结束(别过度设计)
└─ 否 → 能用云存储(S3/OSS/COS)?
├─ 能 → 预签名 URL + Multipart 直传
│ 服务端只做鉴权/记账,30 分钟接完 【推荐】
└─ 不能(私有化)→ 自建分片:
├─ 前端: uppy/WebUploader + Worker hash + 3 并发
├─ 分片: 4-8MB 起步,按吞吐动态调整
├─ 合并: 流式追加,禁止内存拼接
└─ 运维: 孤儿分片定时清理 + 会话幂等
| 项 | 值 |
|---|---|
| 测试文件 | 1GB 随机数据 × 28 组(分片 1/2/4/8/16/32MB × 并发 1/3/6/10) |
| 吞吐(1 并发 → 10 并发) | 868 → 6,082 MB/s(本机内存/CPU 模型,量级参考) |
| 并发收益拐点 | 3-4 并发后边际收益 <10% |
| hash 实测 | 512MB 全量 SHA-256: 0.216s(M 系) / 抽样 hash: 0.1ms |
| 图 2 说明 | 100Mbps / 50ms RTT / 1% 丢片数学模型推演,非实网抓包 |
作者:王亚博(Abel Wang)· 转载请注明出处。测试数据为特定环境结果,你的带宽/硬件不同请自行跑基准。