ExpressJS HTTP Streaming 客户端断连后磁盘被占满问题
问题根因
磁盘空间被快速占满的核心原因是未捕获的流错误触发Node.js疯狂写入错误日志,和业务返回的随机字符串大小无关,具体触发逻辑:
- 客户端断开连接时,HTTP响应流会先抛出
ECONNRESET(连接被对端重置)错误,原代码只监听了close事件,没有监听error事件,这个错误会变成未捕获异常,Node.js默认会把错误栈、缓冲区堆积数据持续写入磁盘日志/临时转储文件,这就是每秒百MB级写入的来源。 - 定时器逻辑完全忽略流状态:连接断开后
res.write()会持续返回false(表示内核发送缓冲区已满,无法再向客户端发送数据),但定时器还是每秒往失效的流里塞数据,这些无效数据不断在缓冲区堆积,进一步放大了错误日志的写入量。 - 客户端代码存在缺陷:只调用了一次
reader.read(),仅消费第一块数据后就不再处理后续流内容,哪怕不主动关闭页面,缓冲区很快就会被打满触发流异常。
修复方案
服务端调整
核心是补全流事件监听、处理背压、所有退出分支都做资源清理:
app.get('/data', (_, res) => { // 配置流式响应头 res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.setHeader('Cache-Control', 'no-cache'); const interval = setInterval(() => { // 写入前判断流状态,流已结束就直接返回 if (res.writableEnded) return; // 处理背压:write返回false表示缓冲区已满,等drain事件后再继续写入 const canWrite = res.write(`${Math.random().toString()}\n`); if (!canWrite) { res.once('drain', () => {}); } }, 1000); // 统一清理逻辑,所有终止场景都触发 const cleanup = () => { clearInterval(interval); if (!res.writableEnded) { res.end(); } }; // 监听所有可能的流终止事件 res.on('close', cleanup); res.on('finish', cleanup); res.on('error', (err) => { // 捕获连接错误,避免错误冒泡触发未捕获异常落盘 console.error('客户端连接异常断开:', err.message); cleanup(); }); });
额外建议在Node.js服务入口加全局错误捕获,兜底所有未处理的异常,避免异常触发系统级日志落盘:
process.on('uncaughtException', (err) => { console.error('全局未捕获异常:', err.message); }); process.on('unhandledRejection', (reason) => { console.error('全局未处理Promise拒绝:', reason); });
客户端调整
补全流循环读取逻辑,不要只读一次就停止消费:
const res = await fetch('/data') const reader = res.body.getReader() const decoder = new TextDecoder(); // 循环读取直到流结束 while (true) { const { value, done } = await reader.read(); if (done) break; console.log(decoder.decode(value)); }
内容的提问来源于stack exchange,提问作者Tran Chien
相关产品推荐
相关产品推荐

