Node.js高并发下的内存泄漏排查与解决实录

发布于:2025-08-06 ⋅ 阅读:(17) ⋅ 点赞:(0)

“线上问题没有小问题,尤其是那种偶发的、不好重现的,最让人头大。”

一、背景介绍

最近我们团队在做一个高并发的接口服务,技术栈是Node.js+Express。上线后,随着访问量的提升,服务突然开始频繁重启,日志里全是“JavaScript heap out of memory”。一开始以为是服务器内存小,结果加了内存还是会挂。很明显,遇到了传说中的——内存泄漏

在这里插入图片描述

二、问题现象

服务刚启动时一切正常,内存占用也不高。但只要一有流量,内存就会慢慢上涨。每次到1.5GB左右,Node进程就崩溃重启。更郁闷的是,重启后又能跑一会儿,再次爆掉,死循环……

三、排查思路

说实话,第一次遇到这种问题,真有点慌。但冷静下来,其实排查思路很清晰:

  1. 确认问题是否必现:用压测工具模拟流量,发现内存确实一直涨。
  2. 定位泄漏点:用Node.js自带的--inspect参数配合Chrome DevTools,抓内存快照,分析堆内对象。
  3. 怀疑第三方依赖:排查了下,发现我们用了一些缓存库和中间件,怀疑有问题。
  4. 代码自查:重点检查了全局变量、闭包、定时器等常见泄漏点。

四、核心排查过程

1. 利用Chrome DevTools抓内存快照

在服务启动参数里加上--inspect,然后用Chrome访问chrome://inspect,连接到Node进程。压测跑一会儿,抓一张快照,再过一会儿再抓一张,比较两次的对象数量和类型。

(图示建议:插一张DevTools内存快照对比的截图)

2. 发现泄漏对象

分析快照发现,有大量的Buffer对象和某个自定义Cache对象一直没被释放。进一步排查代码,发现是有个缓存模块,写了个Map存储请求结果,但忘了加淘汰策略,导致缓存一直涨。

// 问题代码片段
const cache = new Map();
app.get('/api/data', (req, res) => {
  const key = req.query.id;
  if (cache.has(key)) {
    return res.json(cache.get(key));
  }
  // 省略真实查询逻辑
  cache.set(key, result);
  res.json(result);
});

只要有新请求,cache就会越来越大,永远不清理。

3. 修复方案

给缓存加上最大容量,超出就淘汰最早的:

const MAX_CACHE_SIZE = 1000;
const cache = new Map();
app.get('/api/data', (req, res) => {
  const key = req.query.id;
  if (cache.has(key)) {
    return res.json(cache.get(key));
  }
  // 省略真实查询逻辑
  cache.set(key, result);
  // 淘汰策略
  if (cache.size > MAX_CACHE_SIZE) {
    const oldestKey = cache.keys().next().value;
    cache.delete(oldestKey);
  }
  res.json(result);
});

修复后再次压测,内存占用明显稳定,服务不再频繁重启。

五、经验总结

  1. 线上服务要监控内存曲线,及时发现异常。
  2. 缓存一定要有淘汰策略,别偷懒。
  3. 用好Node.js的内存分析工具,定位泄漏对象。
  4. 多做压测和对比快照,不要只靠猜。

六、写在最后

这次内存泄漏虽然折腾了几天,但也让我对Node.js的内存机制和调试工具有了更深的理解。线上环境容不得半点侥幸,大家平时写代码一定要多留心,别让小问题变成大事故。


网站公告

今日签到

点亮在社区的每一天
去签到