第 40 章 / 共 57 章 · 约 4 分钟
设计模式:大文件处理
照片、视频、附件、数据集这类大文件,如果走“客户端 → 应用服务器 → 存储”的老路,应用服务器的带宽、内存和连接会被一点点耗尽,一次网络抖动就要从头再传。这一章讲大文件上传、下载和后处理的标准做法:让字节直接在客户端和对象存储之间流动,应用服务器只负责授权和记账。
1. 上传:预签名 URL 直传
- 客户端请求上传:
POST /uploads {filename, size, content_type}。 - 应用校验权限和大小,在数据库写一条状态为
PENDING的记录,生成对象 key,签发一个有时效的预签名 URL(大文件则签发分段上传的每一段的 URL)。 - 客户端直接 PUT 到对象存储。
- 对象存储的事件通知(或客户端调用
complete)告诉应用上传完成,应用校验大小和哈希后把记录改为UPLOADED,并触发后处理。
图表可横向滚动,展开后可缩放查看。
查看图表源码
flowchart LR
accTitle: 预签名 URL 直传和后处理
c["客户端"] -->|"1. 申请上传"| api["应用服务"]
api -->|"2. 写 PENDING 记录"| db[("元数据库")]
api -->|"2. 预签名 URL"| c
c -->|"3. 直接上传字节"| s3[("对象存储")]
s3 -->|"4. 上传完成事件"| q["队列"]
q --> proc["后处理 worker:校验、转码、缩略图"]
proc --> db
cdn["CDN"] --> s3
viewer["查看者"] -->|"签名的下载 URL"| cdn
2. 大文件和弱网:分段与断点续传
- 分段上传:几 GB 的文件切成 5–100 MB 的段并行上传,失败的段单独重传,全部完成后合并。
- 断点续传:客户端记住已完成的段(或向服务端查询),网络恢复后只传剩下的。手机上传视频时尤其重要。
- 分块加哈希:把文件切成固定大小的块,每块计算哈希。已经存在的块不必再传(去重),修改文件只需上传变化的块,这是网盘同步的基本思路。
3. 下载:CDN 加签名 URL
- 公开内容(商品图片、公开视频)直接经 CDN 缓存分发。
- 私有内容保持桶私有,由应用为有权限的用户签发短时效的下载 URL,或使用 CDN 签名 Cookie。
- 视频切成几秒一段的小文件(HLS/DASH),播放器按网速选择码率,每段都能被 CDN 缓存;拖动进度条只需请求对应的段。
- 大文件下载支持 Range 请求,可以断点续传、多线程下载。
4. 后处理
- 上传完成事件进入队列,worker 做病毒扫描、格式校验、生成缩略图、转码、提取元数据,按长时间运行的任务来设计:幂等、可重试、失败进入死信队列。
- 处理结果写回对象存储(新的 key),元数据库记录各个版本的位置和状态。
- 处理完成前,用户看到的是“处理中”,而不是一个打不开的文件。
5. 一致性和清理
- 记录有、文件没有:上传从未完成的
PENDING记录,超过时限由定时任务清理。 - 文件有、记录没有:上传了但没有确认的对象,用生命周期规则(例如上传目录下超过 1 天的对象自动删除),或者定期对账清理。
- 防篡改:签发 URL 时限定 content-type 和大小上限;完成时校验客户端声明的哈希。
6. 常见追问
- 为什么不让文件经过应用服务器? 一个 1 GB 的上传会占住一个连接几分钟,几百个并发上传就能打满服务器的带宽;直传把这部分负载交给了几乎无限扩展的对象存储。
- 预签名 URL 泄露了怎么办? 有效期设短(几分钟),只授权一个具体的 key 和操作,泄露的影响很有限。
- 怎么限制用户的存储配额? 申请上传时按声明的大小预留配额,完成时按实际大小结算,未完成的超时释放。
- 同一个文件很多人上传? 用内容哈希做 key,相同内容只存一份,按引用计数删除。
7. 本站用到这个模式的案例
- 案例 7:视频流平台:分段直传、上传完成后触发转码,切片经 CDN 分发。
- 案例 4:即时通讯:
POST /media返回预签名 URL,消息本身只携带media_ref。 - 案例 8:网络爬虫:抓取的网页打包成大文件存放,元数据单独存在键值存储里。