大文件上传这个需求,凡是做过几年后端的人基本都会遇到一两次。我印象最深的是以前给一个内部协作平台做升级,用户经常要上传好几个GB的工程打包文件,那时候方案很粗暴,一个大请求直接把整个文件往服务器扔。结果就是传了四十多分钟,中途Wi-Fi闪一下断掉,进度归零,用户崩溃,运维半夜被电话叫醒。后来把断点续传做进去,问题才真正解决。
所谓断点续传,核心思路其实不复杂:把大文件切成若干个分片,逐块上传,服务端单独记录每个分片的状态,中断后只重传未传完的那几块,而不是从头再来。听起来简单,但真正落地时牵扯到分片大小、校验合并、并发控制、临时目录清理、对象存储适配等一系列问题。这篇文章我会把整个链路拆开讲一遍,从HTTP上传的瓶颈到Spring Boot后端实现,再到前端Worker、秒传、MinIO适配这些进阶玩法,目的就是让你看完能直接在自己的项目里动手做一版,而不是只停留在“我知道要分片”这个层面。
1. 大文件上传为什么不能只靠“一个大请求把文件甩给服务器”
1.1 一次2GB文件上传失败后的复盘
先讲个真实场景。某个下午,一位设计师同事要把一个压缩包传到共享盘,文件正好2GB出头。我们当时的实现是用浏览器FormData对象直接把文件放进请求体,后端用Commons FileUpload接收,整个流程走的是同步接口。
传到一半,内存占用飙升,Tomcat那边先是报警,然后请求直接超时断开,前台上传进度条卡在63%。更尴尬的是,服务端检查了一遍,磁盘上根本没有落地的文件,等于白传了半小时。
那次复盘得出的结论很直接:一个大请求上传大文件,链路里全是隐患。首先是内存,很多Servlet容器处理上传时默认会先把请求体缓冲到内存,文件稍微大一点就触发OutOfMemory。然后是超时,无论是Tomcat的连接超时还是Nginx的proxy_read_timeout,都是给普通请求设计的,一个持续半小时的上传请求很容易被掐断。最后是重传成本,网络抖动、用户误关页面、公司断网,任何一个环节出问题,前功尽弃。
1.2 HTTP上传的隐藏瓶颈不只是带宽
很多人以为大文件上传慢是带宽不够,带宽只是其中一环。真正的瓶颈往往在链路中间。
第一个瓶颈是请求体缓冲。Spring Boot内嵌Tomcat默认的maxSwallowSize、maxPostSize如果不去调,大文件直接会被拦截。Nginx层还有一个client_max_body_size,默认才1MB,不设置的话文件根本到不了后端。
第二个瓶颈是连接时间限制。移动端用户切个网络,TCP连接就断了,HTTP请求是面向短连接的,天然不适合长时间传输。
第三个瓶颈是重复传输的成本。单个大请求的方式里,服务端无法知道用户传到了哪个字节,客户端也不清楚服务端收到了多少。链路只要断一次,所有已传输的数据全部作废。这在大文件场景下是致命的,因为重传不只是一次,可能反复出现,用户耐心和满意度会迅速归零。
1.3 断点续传到底解决的是哪几类问题
把问题梳理清楚之后,断点续传的意义就很明确了。它实际上解决的是三类痛点:
- 中断恢复:网络断掉之后,客户端能从已传完的分片继续,而不是从0开始。
- 多路并发:把文件切成多个分片后,可以同时发起多个上传请求,充分利用带宽,缩短总时间。
- 服务端可控性:分片方式下,服务端可以逐个校验、逐块存储,内存占用小,也便于后续做秒传、进度查询这类功能。
所以断点续传不是“炫技”,而是大文件上传场景下的工程必需品。下面我开始拆解具体实现方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 断点续传落地方案:分片、进度记录、校验机制全拆解
2.1 总体架构:三个核心模块怎么配合
一套完整的断点续传方案,至少包含三个模块。
- 分片模块:在前端把文件切块,给每一块编号,记录总块数、当前块号、文件唯一标识。
- 状态模块:服务端记录每个分片的上传状态,“文件已传了哪些块”这个信息必须持久化,不能放在内存里。
- 合并模块:所有分片传完后,服务端按照顺序把它们合并回完整文件,并做完整性校验。
配合关系是客户端先把文件元信息(文件名、大小、分片大小、分片总数、文件MD5)发给服务端,服务端拿到后返回一个文件上传任务的ID,之后客户端逐块发送,服务端逐块保存并更新状态,最后客户端发一个合并请求,服务端完成合并。
这里有一个关键点:文件唯一标识怎么生成。最常见的是用文件内容的MD5或SHA-1值,这样还能顺带做秒传。但大文件算全量MD5很耗时,我一般会在前端用抽样策略快速生成一个近似哈希,后端在最终合并完成后再精确计算一次全量MD5,保证结果准确。
2.2 分片大小怎么选:不是越小越好
分片大小是方案里最容易被忽视的参数,但它直接影响上传成功率和整体效率。
我做过一轮简单对比测试:
| 分片大小 | 总请求数(2GB文件) | 优点 | 缺点 |
|---|---|---|---|
| 1MB | 2000+ | 单块失败影响极小 | 请求数量太多,服务端压力大,HTTP握手开销高 |
| 5MB | 约400 | 均衡 | 适合大多数场景 |
| 20MB | 约100 | 请求数少,服务端压力低 | 单块失败重传成本高,网络抖动时容易反复失败 |
综合来看,我建议普通内网或一般公网环境下选4MB到8MB之间。我们项目里用的是5MB,实测下来成功率比较稳定。如果你所在业务移动端场景多、网络质量不稳定,建议降到2MB,宁可多几次请求,也要减少单块失败的概率。
2.3 服务端如何“记住”已上传的分片
服务端维护上传状态的方案,常见有三种。
- 内存Map:简单但重启即丢,只适合演示。
- 数据库表:一个upload_task表加一个upload_chunk表,记录任务和分片状态,适合分布式部署。
- 本地文件系统:利用目录结构,比如每个任务一个目录,传完一个分片就写一个文件,文件名就是分片序号。
为了讲清楚原理,后文代码示例我用的是第三种,即基于本地文件目录来做状态记录,不依赖数据库,逻辑最直观。生产环境如果把任务信息表放到数据库中,再把分片文件放到OSS或MinIO上,换个存储层就可以了。
需要记录的状态字段大致有这些:任务ID、文件名、文件大小、分片总数、已上传分片数、每个分片的唯一标识、上传时间。
2.4 校验与合并:如何保证文件不损坏
分片上传阶段,每个分片都要
