https://juejin.cn/post/7571355989133099023 掘金文章
大文件上传后端落地方案
Context
这篇文章的核心观点是:大文件上传不能简单设计成一个同步 /upload 接口,而应抽象为“上传任务 + 分片状态 + 可恢复流程 + 异步后处理”的完整系统。目标是支持 GB 级文件、弱网断点续传、秒传、集群部署、失败可恢复、状态可审计,并避免同步上传和同步合并带来的超时、磁盘堆积、状态不一致等问题。
本方案适合作为后续在后端系统中实现大文件上传能力的工程化设计依据。
推荐方案
1. 总体架构
采用分层设计:
- Controller:暴露上传任务、分片上传、进度查询、提交合并、取消上传接口。
- Application Service:编排上传任务状态、幂等校验、权限校验、分片完整性校验。
- Storage Service:屏蔽本地文件、NAS、MinIO、OSS、S3 等存储差异。
- Async Worker:异步执行文件合并、hash 校验、内容安全检测、临时文件清理。
- DB:作为上传任务和分片状态的最终状态源。
- Redis:用于分布式锁、限流、短期进度缓存,不作为最终状态源。
核心原则:应用服务无状态,分片不能依赖单机本地磁盘;生产集群优先使用对象存储或共享存储。
2. 核心接口设计
建议接口拆分如下:
POST /files/multipart/init- 初始化上传任务。
- 入参:
fileName、fileSize、fileHash、chunkSize、totalChunks、contentType、bizType。 - 行为:先检查秒传;若文件已存在,直接返回文件信息;否则创建或复用上传任务,并返回
uploadId和已上传分片列表。
POST /files/multipart/chunks- 上传单个分片。
- 入参:
uploadId、chunkIndex、chunkHash、file。 - 行为:校验任务状态、分片序号、大小、hash;重复上传相同分片直接返回成功;hash 不一致则拒绝。
GET /files/multipart/{uploadId}/progress- 查询上传进度。
- 返回:任务状态、总分片数、已上传分片索引、失败分片、合并状态、错误信息。
POST /files/multipart/{uploadId}/complete- 提交合并。
- 行为:校验分片完整性后,将任务状态改为
MERGING或投递异步合并任务,接口快速返回。
DELETE /files/multipart/{uploadId}- 取消上传。
- 行为:任务状态改为
CANCELLED,异步清理临时分片。
3. 数据库设计
建议至少三张表:
upload_task
保存上传任务主状态。
关键字段:
idupload_id:全局唯一上传任务 IDfile_hashfile_namefile_size:必须使用 Long/BIGINTchunk_sizetotal_chunksuploaded_chunksstatus:INIT / UPLOADING / MERGING / SUCCESS / FAILED / CANCELLED / EXPIREDfile_id:合并成功后的最终文件 IDerror_msgexpire_timecreate_bycreate_timeupdate_time
索引建议:
- 唯一索引:
upload_id - 普通索引:
file_hash, file_size - 普通索引:
status, expire_time
upload_chunk
保存每个分片的上传状态。
关键字段:
idupload_idchunk_indexchunk_hashchunk_sizestorage_pathstatuscreate_timeupdate_time
索引建议:
- 唯一索引:
upload_id, chunk_index
file_info
保存最终文件元数据。
关键字段:
idfile_hashfile_namefile_sizecontent_typestorage_pathurlstatuscreate_bycreate_time
索引建议:
- 普通索引或唯一策略:
file_hash, file_size
是否允许同 hash 文件多业务复用,需要结合权限和租户模型决定。多租户系统不要简单全局秒传,至少要校验租户、用户或业务域权限。
4. 状态机设计
推荐状态流转:
INIT -> UPLOADING -> MERGING -> SUCCESS
| |
v v
FAILED FAILED
|
v
EXPIRED
任意未完成状态 -> CANCELLED约束:
MERGING状态禁止继续接收分片。SUCCESS、CANCELLED、EXPIRED为终态。- 状态更新使用 CAS 条件,例如
where upload_id = ? and status = ?。 complete可以重试,但同一uploadId同一时间只能有一个合并任务执行。
5. 分片上传与断点续传
客户端流程:
- 计算文件 hash。
- 调用 init。
- 根据服务端返回的已上传分片列表跳过已完成分片。
- 并发上传缺失分片。
- 所有分片完成后调用 complete。
- 轮询 progress 或接收通知等待合并完成。
服务端要求:
- 以
uploadId + chunkIndex作为分片幂等键。 - 重复上传相同 hash 的分片直接返回成功。
- 重复上传但 hash 不一致必须拒绝,避免脏数据覆盖。
- 分片大小、分片序号、总分片数必须校验。
- 断点续传依据数据库分片记录,而不是浏览器缓存或服务端内存。
6. 秒传设计
初始化时根据 fileHash + fileSize 查询最终文件表。
命中后还必须校验:
- 文件状态为可用。
- 当前用户、租户或业务域有权限使用该文件。
- 如果文件有安全扫描状态,必须是扫描通过。
通过后直接创建业务引用或返回文件 ID,不再上传分片。
7. 异步合并设计
complete 接口不建议同步合并大文件,避免 HTTP 超时和应用线程阻塞。
推荐流程:
- 校验任务存在且状态为
UPLOADING。 - 校验分片数量完整。
- 使用状态 CAS 改为
MERGING。 - 投递 MQ 或写入合并任务表。
- Worker 获取任务后加分布式锁。
- 按
chunkIndex顺序流式合并,或调用对象存储 multipart complete。 - 合并后重新计算文件 hash,与客户端 hash 比对。
- 写入最终文件表。
- 上传任务改为
SUCCESS。 - 异步删除临时分片。
如果合并失败:
- 记录失败原因。
- 任务改为
FAILED。 - 保留分片一段时间,允许用户重试 complete。
8. 幂等与并发控制
需要重点保证以下幂等场景:
- init 重复调用:返回同一个未完成任务,或命中秒传。
- chunk 重复上传:相同分片相同 hash 返回成功。
- complete 重复调用:已在合并中则返回
MERGING,已成功则返回最终文件。 - abort 重复调用:终态返回成功。
并发控制建议:
- DB 唯一索引防重复分片。
- 状态 CAS 防止并发状态错乱。
- Redis 或 DB 锁防止并发合并。
- 上传分片接口限制单用户、单任务、单租户并发数。
9. 集群部署要求
生产环境不建议把分片存储在应用本机目录。
推荐优先级:
- 对象存储 multipart upload:最适合云环境和大文件。
- MinIO / S3 兼容存储:适合私有化部署。
- NAS / NFS:可用但要关注 IO、锁和稳定性。
- 本地磁盘:仅适合单机开发或测试。
集群部署中:
- DB 是最终状态源。
- Redis 只做锁、限流和缓存。
- Worker 可多实例部署,但同一任务只允许一个 Worker 合并。
- 临时分片路径必须带
uploadId隔离。
10. 安全控制
必须校验:
- 登录态和权限。
- 租户隔离。
- 文件大小上限。
- 分片大小上限。
- 文件后缀白名单或黑名单。
- MIME 类型。
- 文件魔数。
- 文件名路径规范化,禁止
../路径穿越。 - hash 与实际内容一致。
- 上传频率和并发数。
如涉及医疗、政企、知识库等场景,还应增加:
- 病毒扫描。
- 敏感内容检测。
- 文件访问审计。
- 文件加密存储。
- 下载鉴权和防盗链。
11. 清理机制
需要定时任务清理:
- 超过
expire_time的INIT / UPLOADING任务。 - 长时间卡在
MERGING的任务。 FAILED / CANCELLED / EXPIRED的临时分片。- 孤儿分片。
- 未被业务引用的最终文件。
清理任务要求:
- 分页扫描。
- 限速删除。
- 可重试。
- 删除前更新任务状态,避免边上传边删除。
- 记录清理日志和删除结果。
12. 监控告警
核心指标:
- 初始化任务数。
- 分片上传成功率。
- 分片上传平均耗时和 P95/P99。
- 合并任务耗时。
- 合并失败率。
- 秒传命中率。
- 临时分片占用空间。
- 超时任务数量。
- 卡在
MERGING的任务数量。
告警建议:
- 合并失败率突增。
- 临时存储空间超过阈值。
MERGING任务超过指定时长。- 分片上传错误率突增。
- 对象存储访问异常。
日志建议:
- 每个 uploadId 全链路打印。
- chunk 上传失败记录原因。
- complete 和 merge 记录状态流转。
- 清理任务记录删除数量和失败原因。
13. 测试验证
功能测试:
- 普通大文件上传成功。
- 秒传命中。
- 断点续传。
- 重复分片上传。
- 乱序分片上传。
- 缺少分片时提交合并失败。
- 合并成功后 hash 校验正确。
- 取消上传后不能继续上传。
并发测试:
- 同一文件多用户并发 init。
- 同一 uploadId 并发 chunk。
- 同一 uploadId 并发 complete。
- Worker 多实例并发消费。
异常测试:
- 上传中断后恢复。
- 合并中服务重启。
- 对象存储短暂失败。
- Redis 锁失效。
- DB 唯一索引冲突。
- 临时文件被误删。
性能测试:
- 1GB、5GB、10GB 文件上传。
- 不同 chunkSize 下的吞吐与耗时。
- 高并发分片上传。
- 合并阶段 IO 压力。
- 临时存储空间增长与清理速度。
后续实现时建议修改的关键文件
实施前应读取这些文件确认现有普通上传、分片表、文件表、安全文件服务和权限模型,避免重复造轮子。
验证方式
端到端验证建议:
- 本地启动后端服务。
- 使用测试脚本模拟 init、chunk、progress、complete 全流程。
- 使用同一个文件重复上传验证秒传。
- 中断部分分片后重新上传验证断点续传。
- 并发调用 complete 验证幂等和锁。
- 人为删除分片或修改 hash 验证异常路径。
- 观察数据库任务状态、分片记录、最终文件记录是否一致。
- 观察日志、指标和临时文件清理结果。
主要风险
- 只做接口拆分但不做状态机,会导致上传状态不可控。
- 只依赖本机磁盘,会导致集群部署后分片分散。
- 同步合并大文件,会造成接口超时和线程阻塞。
- 没有幂等设计,会在重试和并发场景产生重复数据或脏数据。
- 没有清理机制,会持续占用磁盘或对象存储空间。
- 全局秒传不做权限隔离,可能造成越权访问文件。