NodeJS 文件上传踩坑:大文件超时与内存暴涨
用 NodeJS 写文件上传接口,小文件一切正常,一传大文件就超时、内存飙升甚至进程崩溃。这篇记录完整的排查和优化过程:从默认的内存缓冲问题、框架体积限制、反向代理超时配置,到改用流式写入的正确姿势,并整理了断点续传和磁盘清理的注意事项,适合做文件服务的开发者参考。
# 一、问题现象
一个文件上传接口,测试环境传几兆的图片没问题,上线后用户传几百兆的视频就出问题:先是**请求超时**,然后**内存占用直线上升**,最后进程被系统杀掉。
# 二、为什么会内存暴涨
很多写法的本质是把整个文件**读进内存再处理**:
```js
// 危险写法:整个文件进内存
const chunks = [];
req.on("data", c => chunks.push(c));
req.on("end", () => {
const buf = Buffer.concat(chunks); // 几百兆直接吃满内存
});
```
文件多大就占多大内存,并发几个上传就扛不住了。
# 三、正确做法:流式写入
用管道把请求流直接导向文件写入流,**内存占用恒定**:
```js
const fs = require("fs");
const writeStream = fs.createWriteStream(targetPath);
req.pipe(writeStream);
writeStream.on("finish", () => res.end("ok"));
writeStream.on("error", err => { /* 清理临时文件 */ });
```
这样不管文件多大,内存里同时只有一小块缓冲区。
# 四、超时问题要分层排查
大文件上传超时,通常**不止一处**配置:
| 层级 | 配置项 | 说明 |
| --- | --- | --- |
| 应用 | server timeout | 框架默认超时往往偏短 |
| 反向代理 | 请求体大小限制 | 常见默认仅 1MB |
| 反向代理 | 读写超时 | 需按最大文件预估 |
| 网关/CDN | 上传时限 | 云端服务也有独立限制 |
**逐层放宽才有效**,只改一处经常没用——这是排查时最容易卡住的地方。
# 五、几个必须加的处理
## 1. 大小限制与类型校验
别等传完再判断,在**请求头阶段**就检查内容长度,超限直接拒绝,省流量也省资源。
## 2. 失败要清理临时文件
上传中断时,写了一半的文件会残留在磁盘上。务必在错误回调里删除,并加**定时任务清理过期临时文件**。
## 3. 文件名安全处理
永远不要直接用客户端传来的文件名,要过滤路径穿越字符并重新生成唯一名,否则有**任意文件写入**风险。
## 4. 进度反馈
大文件上传给用户进度提示体验会好很多,可以基于已接收字节数计算,通过单独接口轮询。
# 六、进阶:断点续传
对超大文件,考虑分片上传:
1. 前端把文件切成固定大小的分片;
2. 每片单独上传,服务端按序号暂存;
3. 全部传完后服务端合并;
4. 支持查询已传分片,中断后从断点继续。
这套方案能显著降低失败重传的成本,具体实现时要注意**分片的并发控制和合并时的磁盘空间**。
# 七、避坑清单
1. **绝不用 Buffer 拼接大文件**,一律走流;
2. **超时配置要全链路检查**,从应用到网关;
3. **失败必清理**,临时文件不清理迟早撑爆磁盘;
4. **文件名必须重命名**,别信客户端输入;
5. **上传目录与代码目录隔离**,避免上传的文件被执行。
# 八、小结
文件上传看起来简单,要做稳得考虑流式处理、超时链路、失败清理和安全校验这几个方面。把这套做扎实,上传功能才算真正可用。