如何在nodejs中生成快照

1. 内置支持

1.1v8.writeHeapSnapshot 主动函数调用11.13.0

const { writeHeapSnapshot } = require('node:v8');
const {
  Worker,
  isMainThread,
  parentPort,
} = require('node:worker_threads');

if (isMainThread) {
  const worker = new Worker(__filename);

  worker.once('message', (filename) => {
    console.log(`worker heapdump: ${filename}`);
    // Now get a heapdump for the main thread.
    console.log(`main thread heapdump: ${writeHeapSnapshot()}`);
  });

  // Tell the worker to create a heapdump.
  worker.postMessage('heapdump');
} else {
  parentPort.once('message', (message) => {
    if (message === 'heapdump') {
      // Generate a heapdump for the worker
      // and return the filename to the parent.
      parentPort.postMessage(writeHeapSnapshot());
    }
  });
}

这个使用场景比较广泛了,可以写一个接口,让后调用接口后,执行这个函数。

也可以在某些事件监听里面触发。

也可以定时触发。

1.2--heap-prof 命令行选项12.4.0

node --heap-prof --heap-prof-dir=./heap --heap-prof-name=TEST.HEAP.heapprofile index.js

开启后,默认在主动退出程序时,会生成快照,异常退出无法生成。

可选配置

--heap-prof-dir 生成目录

--heap-prof-name 生成名称

--heap-prof-interval 指定 --heap-prof 生成的堆分析文件的平均采样间隔(以字节为单位)。默认为 512 * 1024 字节。

1.3--heapsnapshot-signal=signal12.0.0

触发快照的信号,可以自定义控制生成时机

# 启动
node --heapsnapshot-signal=SIGUSR2 index.js

# 触发信号
kill -SIGUSR2 <pid>

虽然名为 "kill",但这个命令实际上是用来向进程发送信号的通用工具。它不仅仅用于终止进程,还可以发送各种其他信号。IGKILL (9):这个信号确实会终止进程。但还有许多其他信号不会终止进程(但是需要注意的是k8s内监听到未处理的SIGUSR信号会直接 kill掉进程,导致 pod重启),如 SIGUSR1, SIGUSR2 等。

同样的发送信号,可以自己手动监听一下SIGUSR2, 或者写一个接口。 接口可以更好的自定义生成时机和规则。

另外注意,通过信号发送时注意,一些node的库也会发送 SIGUSR2 信号。比如 nuxt 的 HMR 更新,或者是 pm2 重启都会发送SIGUSR2信号。所以如果想要更加完善定制,建议自己写接口。

1.4--heapsnapshot-near-heap-limit=max_count 命令行选项14.18.0

当 V8 堆使用率接近堆限制时,将 V8 堆快照写入磁盘,参数max_count表示最多快照数量,接近限制后连续生成,但是不会超过设置参数

对比其他方案

  • 优点

    自动生成,无须采样,定时生成快照,在因为内存溢出导致程序退出时很方便。但是通常情况下,内存极速增长的情况下,往往来不及生成快照,就会内存溢出导致剩下退出。

  • 缺点

    无法自定义配置,生成快照名称快照目录, 只能生成在根目录,程序如果在容器内启动,快照可能会随着容器退出而销毁。

2. 第三方支持

2.1node-heapdump

node-heapdump 最后更新时间为2022.8。

3. 其他

3.1 快照对于性能的影响

  1. CPU 使用率暂时性飙升

    • 生成快照时需要遍历整个堆内存
    • 在快照生成期间,CPU 使用率会显著增加
    • 通常持续时间取决于堆内存大小,一般在几秒到几十秒不等
  2. 内存占用临时增加

    • 生成快照时需要额外的内存空间来存储快照数据
    • 快照大小通常与当前堆内存使用量相当
    • 可能导致内存使用量短暂翻倍
  3. 服务响应延迟

    • 由于 JavaScript 是单线程的,生成快照时会阻塞主线程
    • 会导致请求响应时间增加
    • 建议在低峰期或者使用 Worker 线程生成快照
  4. I/O 影响

    • 将快照写入磁盘时会产生 I/O 操作
    • 可能影响其他依赖磁盘 I/O 的操作

最佳实践建议:

  • 在非关键时期(如凌晨)生成快照
  • 使用 Worker 线程生成快照,减少对主线程的影响
  • 控制快照生成频率,避免频繁生成
  • 及时清理历史快照文件,避免占用过多磁盘空间
  • 在生产环境使用前,建议在测试环境评估影响

3.2 coredump方案

如果 node的快照无法生成快照,可以考虑使用coredump方案, Linux 系统在程序崩溃时的一种保护机制,它会将程序当时的内存状态、寄存器状态等信息转储到磁盘上,形成一个 core 文件。这个文件包含了程序崩溃时的完整内存映像,对于事后分析问题非常有帮助。