4.7 GB 视频导致浏览器内存溢出(OOM)的解决方案
在线教育平台大文件视频上传解决方案
场景
我们要做一个讲师后台的课程视频批量上传功能。
需求听起来很简单:一次拖 20 个 4K 视频,浏览器稳、速度快、断网可续传。
第一版我图省事,用了:
<input type="file" />
配合:
const formData = new FormData();
formData.append('file', file);
fetch('/upload', { method: 'POST', body: formData });
结果上线当天就爆了:
-
讲师 A:上传一个 4.7 GB 的
.mov
→ Chrome 直接内存溢出崩溃 -
讲师 B:断网 3 分钟 → 重新上传进度条从 0% 开始,心态崩
-
运营同事:疯狂 @ 前端,“是不是没做分片?”
于是,第二版我设计了三层防线,核心思路就是——把大文件切成小片,让浏览器分开调用。
第一层防线:分片 + 并发控制
思路:4 GB 文件切成一堆小块(比如每块 2 MB),同时上传 5 块,既不卡浏览器也不占满带宽。
// slice.js
const CHUNK_SIZE = 2 * 1024 * 1024; // 2 MB 一块export async function* sliceFile(file) {let cur = 0;while (cur < file.size) {yield file.slice(cur, cur + CHUNK_SIZE);cur += CHUNK_SIZE;}
}
// uploader.js
import pLimit from 'p-limit';
import { sliceFile } from './slice.js';export async function upload(file) {const limit = pLimit(5); // 同时最多 5 个请求const hash = await calcHash(file); // 用于秒传 + 断点续传const tasks = [];for await (const chunk of sliceFile(file)) {tasks.push(limit(() => uploadChunk({ hash, chunk })));}await Promise.all(tasks);await mergeChunks(hash, file.name); // 通知后端合并
}
📌 好处:
-
file.slice
原生 API,不占额外内存 -
p-limit
控制并发,避免一次性发出几百个请求打爆浏览器 -
calcHash
用 WebWorker 算 MD5,不会卡 UI
第二层防线:断点续传
断点续传要解决的关键问题:续在哪?
我们需要一个“进度表”,记录哪些分片已经上传。
存储位置 | 内容 | 生命周期 |
---|---|---|
前端 IndexedDB | hash → 已上传分片索引数组 | 本地有效,清缓存失效 |
后端 Redis/MySQL | hash → 已接收分片索引数组 | 可设置 TTL,支持跨设备续传 |
交互流程:
sequenceDiagramparticipant F as 前端participant B as 后端F->>B: POST /prepare {hash, totalChunks}B-->>F: 200 OK {uploaded: [0,3,7]}loop 上传剩余分片F->>B: POST /upload {hash, index, chunkData}B-->>F: 200 OKendF->>B: POST /merge {hash}B-->>F: 200 OK
第三层防线:协议可插拔
上传逻辑抽象成一个统一接口:
interface Uploader {prepare(file: File): Promise<PrepareResp>;upload(chunk: Blob, index: number): Promise<void>;merge(): Promise<string>; // 返回文件 URL
}
这样我们可以快速切换:
-
BrowserUploader
→ 纯前端分片上传 -
TusUploader
→ tus.io 协议,天然断点续传 -
AliOssUploader
→ 直传阿里 OSS,利用 SDK 自带的断点续传
方案 | 并发控制 | 断点续传 | 秒传 | 代码量 |
---|---|---|---|---|
自研 | 手动实现 | 自己实现 | 手动 | 300 行 |
tus | 内置 | 协议级 | 需后端 | 100 行 |
OSS | 内置 | SDK 级 | 自动 | 50 行 |
加强功能
1. 秒传(Instant Upload)
先算 hash → 调 /exists?hash=xxx
→ 如果已存在,直接返回 URL,不走上传流程。
2. 加密上传
在 uploadChunk
前用 AES-GCM
加密数据块,后端存加密内容,下载时前端解密。
3. P2P 协同上传
用 WebRTC 让同一局域网的浏览器互传分片,然后统一上报,节省出口带宽。
小结
大文件上传的核心不是“传”,而是“断”。
-
把大文件切成 2 MB 小块
-
记住哪些块已经上传
-
支持灵活切换上传协议
这样,浏览器就能稳稳地“吃下”任何体积的视频,哪怕是 4 GB 以上的大文件。