Node.js 内存泄漏排查记

上周值班,监控报警说内存占用超过 80%。看了一眼 Grafana,内存在过去几天里缓慢上升,典型的内存泄漏。

定位问题

首先想到的是最近的代码变更。查了 Git 记录,三天前有个同事改了缓存逻辑。

看了眼代码:

const cache = {};

function getData(key) {
  if (!cache[key]) {
    cache[key] = fetchData(key);
  }
  return cache[key];
}

好家伙,无限增长的缓存。key 是用户 ID,每来一个新用户就往里塞一条,永远不清理。

验证

先用 node --inspect 启动服务,然后用 Chrome DevTools 连上去,看一下堆内存快照。

果然,cache 对象越来越大,里面存了几十万个用户数据。

临时修复

加上过期时间:

const cache = new Map();

function getData(key) {
  const item = cache.get(key);
  if (item && Date.now() - item.time < 3600000) { // 1小时过期
    return item.data;
  }
  const data = fetchData(key);
  cache.set(key, { data, time: Date.now() });
  return data;
}

然后加了个定时清理:

setInterval(() => {
  const now = Date.now();
  for (const [key, item] of cache.entries()) {
    if (now - item.time > 3600000) {
      cache.delete(key);
    }
  }
}, 600000); // 10分钟清理一次

上线后内存稳定了。

长期方案

临时方案虽然能用,但不够优雅。后来用 Redis 做了分布式缓存,内存问题彻底解决。

一些工具

排查 Node.js 内存泄漏,常用的工具有:

  1. node --inspect + Chrome DevTools:最直观
  2. heapdump:生成堆快照文件,离线分析
  3. clinic.js:一个诊断工具套件,推荐试试
npm install -g clinic
clinic doctor -- node app.js

它会生成一个可视化的报告,帮你定位问题。

经验总结

  1. 缓存一定要有过期策略,除非你确定它不会无限增长
  2. 内存监控很重要,提前发现问题比线上炸了再修强
  3. 代码 Review 的时候多注意全局变量和闭包

这次的问题其实挺低级的,但线上跑了好几天才爆出来。说明我们的监控和测试覆盖都还不够。