2598 字
约 8 分钟
3
从 1GB 文件上传看分片上传、断点续传与异步处理

从 1GB 文件上传看分片上传、断点续传与异步处理

在很多系统中,用户需要上传几十 MB、几百 MB,甚至几个 GB 的文件。如果直接通过一次 HTTP 请求上传整个文件,会遇到很多问题:

  • 文件太大,请求持续时间很长;
  • 网络短暂中断后,需要重新上传;
  • 应用服务器可能占用大量内存;
  • 上传接口容易超时;
  • 文件解析、文本切片、向量化等后续任务会拖慢接口响应。

因此,比较成熟的做法是:

分片上传 + 断点续传 + 对象存储 + 状态管理 + 异步处理。

下面以一个 1GB 文件为例,介绍这套方案是如何工作的。

一、先把大文件切成小分片

假设每个分片大小为 5MB,那么 1GB 文件大约会被切成 205 个分片。

file.part-001
file.part-002
file.part-003
...
file.part-205

前端负责切片,并发上传多个分片。每个分片请求中通常会携带:

fileHash      整个文件的哈希
uploadId      本次上传任务的唯一 ID
partNumber    分片序号
partHash      当前分片的哈希

分片大小不是固定的。小分片更容易重试,但请求数量更多;大分片请求数量少,但单次失败需要重传的数据更多。实际项目中常见的大小有 5MB、10MB、20MB 或更大。

二、文件哈希和分片哈希分别做什么

很多人会把 MD5 理解成“判断分片属于哪个文件”,但实际职责可以进一步拆分。

1. 整个文件的哈希

整个文件的 MD5 或 SHA-256 可以用于:

  • 判断两个文件内容是否相同;
  • 实现秒传;
  • 断点续传时找到之前的上传任务;
  • 防止同一个文件被重复上传。

例如,前端计算出:

fileHash = abc123

后端可以先查询这个文件是否已经上传完成。如果已经存在,就不必重复上传。

2. 单个分片的哈希

分片哈希主要用于校验:

  • 分片内容是否完整;
  • 网络传输过程中是否损坏;
  • 重试上传时内容是否一致。

但分片属于哪个上传任务,通常不是单纯依靠 MD5,而是依靠:

uploadId + partNumber

其中 uploadId 标识一次上传任务,partNumber 标识分片顺序。

三、MinIO 负责保存文件分片

后端收到分片后,可以将它保存到 MinIO 中,例如:

uploads/{uploadId}/part-001
uploads/{uploadId}/part-002
uploads/{uploadId}/part-003

更推荐的方式是:后端只负责创建上传任务并生成预签名 URL,前端直接把分片上传到 MinIO。

这样可以减少应用服务器的带宽、内存和 CPU 压力。

整体流程如下:

flowchart TD
    A[前端切片] --> B[创建上传任务]
    B --> C[获得 uploadId 和上传地址]
    C --> D[并发上传分片到 MinIO]
    D --> E[记录分片完成状态]
    E --> F[检查是否全部完成]
    F --> G[MinIO 服务端合并]
    G --> H[更新 MySQL 文件状态]
    H --> I[发送 Kafka 消息]
    I --> J[异步解析和向量化]

四、Redis Bitmap 如何实现断点续传

Redis Bitmap 很适合记录分片上传进度。

假设一个文件有 8 个分片:

分片序号:  0 1 2 3 4 5 6 7
上传状态:  1 1 0 1 0 0 1 0

其中 1 表示已经上传成功,0 表示还没有上传。

当网络断开后,前端重新请求上传状态:

GET /upload/status?uploadId=xxx

后端返回已经完成的分片:

[0, 1, 3, 6]

前端只需要补传第 2、4、5、7 片,不需要从头上传。

这就是断点续传。

不过,Redis Bitmap 更适合作为高速缓存,而不建议作为唯一的数据来源。因为 Redis 可能过期、被淘汰,或者因为故障导致数据丢失。

生产系统通常会采用以下组合:

  • Redis Bitmap:快速查询上传进度;
  • MySQL 分片表或 MinIO Multipart 状态:保存可恢复的信息;
  • 定时校准任务:发现 Redis 和实际对象不一致时重新修复。

五、MySQL 管理文件的业务状态

MySQL 主要保存文件元信息和业务状态,例如:

file_id
file_name
file_size
file_hash
upload_id
uploader_id
organization_id
status
object_key
created_at
updated_at

状态可以设计成:

INIT
UPLOADING
MERGING
COMPLETED
MERGE_FAILED
PROCESSING
PROCESSING_FAILED

需要注意的是,上传状态、合并状态和解析状态最好不要混在一起。

例如:

上传完成 ≠ 文件解析完成
文件合并成功 ≠ 向量化成功

这样系统出现问题时,才能准确判断到底是哪一步失败。

六、MinIO 合并是否具备原子性

MinIO 的 composeObject 可以在存储端直接把多个对象合并成一个新对象,不需要应用服务器把文件全部读出来。

它具有较好的对象层原子性:

  • 合并完成后,目标对象才对外可见;
  • 合并失败通常不会产生一个“半成品”的最终对象;
  • 应用服务器不需要承载整个文件的数据流。

但是,这并不意味着整个业务具备分布式事务。

例如,可能出现:

MinIO 合并成功
MySQL 更新失败

也可能出现:

MySQL 显示已完成
Kafka 消息发送失败

因此,正确做法不是依赖单一组件的原子性,而是采用:

  • 明确的状态机;
  • 幂等接口;
  • 可重试操作;
  • 超时检测;
  • 定时校准任务。

七、合并失败如何处理

合并时可以按照下面的步骤执行:

  1. 将 MySQL 状态更新为 MERGING
  2. 检查所有分片是否存在;
  3. 调用 MinIO 的合并接口;
  4. 校验最终文件大小、分片数量或哈希;
  5. 更新 MySQL 状态为 COMPLETED
  6. 发送后续处理消息;
  7. 最后清理临时分片和 Redis 记录。

如果合并失败,不要立即删除分片,而是:

MERGING → MERGE_FAILED

同时保留:

  • 分片文件;
  • 上传任务记录;
  • 错误原因;
  • 重试次数;
  • 最近一次重试时间。

之后可以由用户主动重试,或者由后台任务自动重试。

如果目标文件已经成功生成,但 MySQL 更新失败,下一次重试时应先检查目标对象是否存在,避免重复创建。也就是说,合并接口必须具备幂等性。

八、合并完成后,Kafka 负责异步处理

文件合并成功后,可以发送一条 Kafka 消息:

{
  "fileId": 10001,
  "objectKey": "files/10001.pdf",
  "fileHash": "abc123"
}

后台消费者收到消息后,执行:

下载或读取文件
    ↓
文件解析
    ↓
文本切片
    ↓
生成向量
    ↓
写入向量数据库
    ↓
更新处理状态

这样上传接口不需要等待这些耗时任务完成,用户可以更快得到“上传成功”的反馈。

如果解析失败,可以设计成:

PROCESSING → PROCESSING_FAILED

然后通过 Kafka 重试、死信队列或人工重新触发处理。

九、如何避免超大文件导致 OOM

避免 OOM 的核心原则是:

不要把整个文件一次性读进应用服务器内存。

具体措施包括:

  • 使用流式读写;
  • 使用固定大小的缓冲区;
  • 前端直接上传到 MinIO;
  • 使用 MinIO 或 S3 原生 Multipart Upload;
  • 限制单个用户和全局并发数;
  • 限制后台解析任务的并发度;
  • 避免调用 getBytes() 这类一次性读取方法;
  • 合并时使用 MinIO 的服务端能力;
  • 对超大文件采用分批解析和分批向量化。

无论文件是 1GB 还是 10GB,应用服务器都不应该因为文件大小而分配同等规模的内存。

十、市面上的主流方案

目前主流方案主要有以下几类:

1. S3 或 MinIO Multipart Upload

这是最常见的对象存储方案,支持:

  • 分片上传;
  • 失败重试;
  • 断点续传;
  • 服务端合并;
  • 分片校验;
  • 未完成任务清理。

2. 云厂商 OSS 分片上传

阿里云 OSS、腾讯云 COS、七牛云等都提供类似能力,通常由官方 SDK 完成大部分细节。

3. 预签名 URL 直传

后端生成临时上传地址,前端直接上传对象存储。后端不经过文件数据流,是现在比较推荐的架构。

4. tus 协议

tus 是一种开放的可续传上传协议,适合需要自建上传服务、并希望统一客户端行为的场景。

5. 前端上传组件

例如一些支持切片、并发、重试和断点续传的前端库,可以减少前端重复开发。

总结

一套比较成熟的大文件上传方案通常是:

文件哈希
    ↓
创建 uploadId
    ↓
文件切片
    ↓
分片直传 MinIO
    ↓
Redis 加速记录进度
    ↓
数据库保存业务状态
    ↓
MinIO 服务端合并
    ↓
状态更新为 COMPLETED
    ↓
Kafka 异步解析和向量化
    ↓
失败重试与定时校准

这套设计的本质是:

把一个容易失败的大操作,拆分成多个小的、可重试、可恢复、可校准的操作。

最终需要重点保证四件事:

  1. 分片上传可以断点续传;
  2. 合并过程具备幂等性;
  3. 上传、合并、解析状态彼此独立;
  4. 任何失败都能重试、恢复或被后台任务发现。
从 1GB 文件上传看分片上传、断点续传与异步处理
http://clxhxhhr.top/posts/623/
作者
clxstart
发布于
2026-09-15
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。