前面介绍了 heapdump 和 memwatch-next 的用法,但在实际使用时并不那么方便,你总不能一直盯着服务器状况,发现内存持续增长并超过了你心里的阈值时,手动的去触发 Core Dump 吧?大多数情况下当你发现问题时,已经错过了现场。
那你可能需要 cpu-memory-monitor。顾名思义,这个模块可以用来监控 CPU 和 Memory 的使用情况,并可以根据配置策略自动 dump CPU 的使用情况(cpuprofile)和内存快照(heapsnapshot)。
我们先来看看 cpu-memory-monitor 怎么使用,其实很简单,只需要在进程启动的入口文件引入以下代码即可:
require('cpu-memory-monitor')({
cpu: {
interval: 1000,
duration: 30000,
threshold: 60,
profileDir: '/tmp',
counter: 3,
limiter: [5, 'hour']
}
})
含义:每 1000ms(interval)检查一次 CPU 使用情况,如果发现连续 3(counter)次 CPU 使用率大于 60%(threshold),则 dump 30000ms(duration) CPU 的使用情况,生成 cpu-${process.pid}-${Date.now()}.cpuprofile
到 /tmp(profileDir) 目录下,1(limiter[1]) 小时最多 dump 5(limiter[0]) 次。
上面是自动 dump CPU 使用情况的策略。Memory 的同理:
require('cpu-memory-monitor')({
memory: {
interval: 1000,
threshold: '1.2gb',
profileDir: '/tmp',
counter: 3,
limiter: [3, 'hour']
}
})
含义:每 1000ms(interval) 检查一次 Memory 使用情况,如果发现连续 3(counter) 次 Memory 大于 1.2gb(threshold),则 dump 一次 Memory,生成 memory-${process.pid}-${Date.now()}.heapsnapshot
到 /tmp(profileDir) 目录下,1(limiter[1]) 小时最多 dump 3(limiter[0]) 次。
注意:memory 的配置没有 duration 参数,因为 Memroy 的 dump 是某一时刻而不是一段时间的。
那聪明的你肯定会问了:能不能 cpu 和 memory 配置一块用?比如:
require('cpu-memory-monitor')({
cpu: {
interval: 1000,
duration: 30000,
threshold: 60,
...
},
memory: {
interval: 10000,
threshold: '1.2gb',
...
}
})
答案是:可以,但不要这么做。因为可能会出现这种情况:内存高了 -> 达到阈值,触发 Memory Dump/GC -> 导致了 CPU 高 -> 达到阈值,触发 CPU Dump -> 堆积的请求越来越多(比如内存中堆积了很多 SQL 查询)-> 触发 Memory Dump -> 雪崩。
通常情况下,只需要用其中一种就可以了。
cpu-memory-monitor 的源代码不过百余行,大体逻辑如下:
...
const processing = {
cpu: false,
memory: false
}
const counter = {
cpu: 0,
memory: 0
}
function dumpCpu(cpuProfileDir, cpuDuration) { ... }
function dumpMemory(memProfileDir) { ... }
module.exports = function cpuMemoryMonitor(options = {}) {
...
if (options.cpu) {
const cpuTimer = setInterval(() => {
if (processing.cpu) {
return
}
pusage.stat(process.pid, (err, stat) => {
if (err) {
clearInterval(cpuTimer)
return
}
if (stat.cpu > cpuThreshold) {
counter.cpu += 1
if (counter.cpu >= cpuCounter) {
memLimiter.removeTokens(1, (limiterErr, remaining) => {
if (limiterErr) {
return
}
if (remaining > -1) {
dumpCpu(cpuProfileDir, cpuDuration)
counter.cpu = 0
}
})
} else {
counter.cpu = 0
}
}
})
}, cpuInterval)
}
if (options.memory) {
...
memwatch.on('leak', () => {
dumpMemory(...)
})
}
}
可以看出:cpu-memory-monitor 没有用到什么新鲜的东西,还是之前讲解过的 v8-profiler、heapdump、memwatch-next 组合使用而已。
有几点需要注意下:
- 只有传入了 cpu 或者 memory 的配置,才会去监听相应的 CPU 或者 Memory。
- 传入 memory 配置时,用了 memwatch-next 额外监听了 leak 事件,也会 dump Memory,格式是
leak-memory-${process.pid}-${Date.now()}.heapsnapshot
。 - 顶部引入了 heapdump,所以即使没有 memory 配置,也可以通过
kill -USR2 <PID>
手动触发 Memory Dump。
下一节:3.1 Promise