对于一个后端开发来说,要时刻关注服务器资源CPU、内存、硬盘。
那怎么获取到资源的信息呢?
其实通过 node 的原生 api 就可以做到。
CPU
js
os.cpus()
我们创建一个nest服务:
js
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import * as os from 'os';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get('status')
status() {
return os.cpus();
}
}

返回的数组元素个数就是 cpu 数。
那具体的属性是什么意思呢?
times.user、times.sys、times.idle 分别代表用户代码占用的 cpu 时间、系统代码占用的 cpu 时间,空闲的 cpu 时间。
基于这些就能算出 cpu 的使用率、空置率来。
js
@Get('status')
status() {
const cpus = os.cpus();
const cpuInfo = cpus.reduce(
(info, cpu) => {
info.cpuNum += 1;
info.user += cpu.times.user;
info.sys += cpu.times.sys;
info.idle += cpu.times.idle;
info.total += cpu.times.user + cpu.times.sys + cpu.times.idle;
return info;
},
{ user: 0, sys: 0, idle: 0, total: 0, cpuNum: 0 },
);
const cpu = {
cpuNum: cpuInfo.cpuNum,
sys: ((cpuInfo.sys / cpuInfo.total) * 100).toFixed(2),
used: ((cpuInfo.user / cpuInfo.total) * 100).toFixed(2),
free: ((cpuInfo.idle / cpuInfo.total) * 100).toFixed(2),
};
return cpu;
}
用 reduce 方法累加 cpu 的数量、user、sys、idle 以及总的 cpu 时间。
然后 cpu 的系统使用率就是 sys/total,用户使用率是 user/total 而空置率就是 idle/total。

内存
memoryUsage 核心作用
process.memoryUsage() 是 Node.js 内置的 process 模块提供的 API,用于实时获取当前 Node.js 进程的内存使用详情,返回一个包含多个关键指标的对象,是排查内存泄漏、评估服务内存占用的核心工具。
它不需要安装任何依赖,直接调用即可。
js
const memoryInfo = process.memoryUsage();
console.log('内存使用详情:', memoryInfo);
// 输出示例(单位:字节)
// {
// rss: 45000000, // 常驻集大小
// heapTotal: 18000000, // 堆总内存
// heapUsed: 9000000, // 堆已使用内存
// external: 1200000, // 外部内存
// arrayBuffers: 100000 // ArrayBuffer 占用内存
// }
所有字段默认单位是 字节 (Bytes) ,可转换为 MB(除以 1024*1024)更易读,各字段含义如下:
| 字段名 | 中文释义 | 核心说明 |
|---|---|---|
rss |
常驻集大小 (Resident Set Size) | Node.js 进程占用的物理内存总大小(包括堆、栈、外部库、代码段等),是操作系统视角的内存占用 |
heapTotal |
V8 堆总内存 | V8 引擎为 JavaScript 对象分配的总堆内存空间(已申请但未必全部使用) |
heapUsed |
V8 堆已使用内存 | V8 引擎实际用于存储 JavaScript 对象(变量、函数、数组等)的内存,排查内存泄漏的核心指标 |
external |
外部内存 | V8 管理的、不在 V8 堆中的内存(如 Buffer 数据、C++ 扩展占用的内存) |
arrayBuffers |
ArrayBuffer 内存 | 所有 ArrayBuffer 和 SharedArrayBuffer 占用的内存(属于 external 的子集) |
heapTotal 代表 V8 引擎为 JavaScript 对象分配的总堆内存空间 (已向操作系统申请但未必全部使用)。V8 采用动态扩容 机制,当满足以下条件时,heapTotal 会自动增大:
- 堆内存不足 :当前
heapUsed(已使用堆内存)接近heapTotal时,V8 会判断「现有堆空间不够用」,主动向操作系统申请更多内存,导致heapTotal上升; - 垃圾回收(GC)后仍不足 :V8 会先触发垃圾回收,尝试释放无用内存。如果 GC 后
heapUsed依然接近heapTotal上限,就会触发堆扩容; - 大对象分配:程序中创建大数组、大对象(比如一次性加载大量数据)时,V8 会直接扩容堆空间以容纳这些对象。
Node.js 默认限制 V8 堆内存(64 位系统约 1.4GB,32 位 0.7GB),可通过启动参数调整,适用于大内存场景:
js
# 将 V8 老年代堆内存上限调整为 4GB
node --max-old-space-size=4096 your-app.js
系统内存
上面是nodejs的进程的内存,使用os可以获取服务器的内存情况。
js
bytesToGB(bytes) {
const gb = bytes / (1024 * 1024 * 1024);
return gb.toFixed(2);
}
getMemInfo() {
const totalMemory = os.totalmem();
const freeMemory = os.freemem();
const usedMemory = totalMemory - freeMemory;
const memoryUsagePercentage = (((totalMemory - freeMemory) / totalMemory) * 100).toFixed(2);
const mem = {
total: this.bytesToGB(totalMemory),
used: this.bytesToGB(usedMemory),
free: this.bytesToGB(freeMemory),
usage: memoryUsagePercentage,
};
return mem;
}
// 结果GB
"mem": {
"total": "24.00",
"used": "23.73",
"free": "0.27",
"usage": "98.88"
},
可对比进程内存与系统总内存,评估资源占用比例:
js
const processMem = (process.memoryUsage().rss / os.totalmem() * 100).toFixed(2);
内存泄露
内存泄漏指的是:Node.js 进程中已分配的内存,在不再被使用的情况下,无法被 V8 垃圾回收(GC)机制释放,导致 heapUsed 持续上涨,最终耗尽 V8 堆内存上限,触发 JavaScript heap out of memory 错误。
V8 对堆内存有默认上限(可通过启动参数调整):
- 64 位系统:默认约 1.4 GB(老年代堆);
- 32 位系统:默认约 0.7 GB;
- 如果
heapTotal达到上限后,heapUsed仍持续增长,会抛出JavaScript heap out of memory错误(内存溢出)。
以下是最常见的内存泄露的场景:
1. 未清理的全局变量
Node.js 中未声明的变量会自动挂载到 global 对象上,成为全局引用,永远不会被 GC 回收。
js
// 错误示例:未声明变量,自动挂载到 global
function loadData() {
// 没有 let/const/var,data 成为全局变量
data = Array(1000000).fill({ name: 'leak' });
}
// 调用后,data 一直存在于全局,无法回收
loadData();
2. 未移除的事件监听器
EventEmitter 的事件监听如果重复绑定、且未在使用后移除,会保留回调函数的引用,导致关联对象无法回收。
js
const EventEmitter = require('events');
const emitter = new EventEmitter();
// 错误示例:每次调用都新增监听器,旧监听器无法回收
function addListener() {
// 匿名回调引用了外部大对象
const bigData = Array(1000000).fill('leak');
emitter.on('data', () => {
console.log(bigData);
});
}
// 多次调用,监听器堆积,内存暴涨
for (let i = 0; i < 10; i++) {
addListener();
}
3. 缓存未设置过期 / 清理策略
手动实现的缓存(比如对象 / Map)如果只加不减,会无限膨胀,导致内存泄漏。
js
// 错误示例:缓存只存不取,无限增长
const cache = new Map();
function setCache(key) {
const data = Array(1000000).fill('leak');
cache.set(key, data);
// 没有清理逻辑,缓存永远不会被回收
}
// 持续添加缓存,内存耗尽
let key = 0;
setInterval(() => {
setCache(key++);
}, 100);
修复:设置缓存过期时间,或限制缓存大小:
js
const cache = new Map();
const MAX_CACHE_SIZE = 100; // 限制缓存最大数量
function setCache(key) {
const data = Array(1000000).fill('leak');
// 超过上限时清理最早的缓存
if (cache.size >= MAX_CACHE_SIZE) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
cache.set(key, data);
}
还有很多导致内存泄露的原因,比如未正确销毁的数据库连接、未结束的流(Stream)等等
内存泄漏的核心特征是 heapUsed 持续上涨且不回落」,可通过以下方式监控识别:
js
// 每5秒输出 heapUsed,观察趋势
setInterval(() => {
const { heapUsed, heapTotal } = process.memoryUsage();
console.log(`heapUsed: ${(heapUsed / 1024 / 1024).toFixed(2)} MB, heapTotal: ${(heapTotal / 1024 / 1024).toFixed(2)} MB`);
}, 5000);
判断标准:
- 正常情况:
heapUsed涨涨跌跌(GC 会定期释放); - 泄漏情况:
heapUsed持续上升,即使触发 GC 也只小幅回落,最终接近heapTotal上限。
硬盘
这里用到 node-disk-info 这个包:
js
npm install --save node-disk-info
getDiskStatus() {
const disks = nodeDiskInfo.getDiskInfoSync();
const sysFiles = disks.map((disk) => {
return {
mounted: disk.mounted,
filesystem: disk.filesystem,
total: this.bytesToGB(disk.blocks) + 'GB',
used: this.bytesToGB(disk.used) + 'GB',
free: this.bytesToGB(disk.available) + 'GB',
usage: ((disk.used / disk.blocks || 0) * 100).toFixed(2),
};
});
return sysFiles;
}

分别是路径、文件系统、总大小、已用大小、可用大小、已用百分比:

系统其他信息
js
@Get('status')
async status() {
return {
cpu: this.getCpuInfo(),
mem: this.getMemInfo(),
dist: await this.getDiskStatus(),
sys: this.getSysInfo()
}
}
getSysInfo() {
return {
computerName: os.hostname(),
computerIp: this.getServerIP(),
osName: os.platform(),
osArch: os.arch(),
};
}
getServerIP() {
const nets = os.networkInterfaces();
for (const name of Object.keys(nets)) {
for (const net of nets[name]) {
if (net.family === 'IPv4' && !net.internal) {
return net.address;
}
}
}
}
这里的 os.networkInterfaces 是拿到所有网卡信息:

从中过滤出非 IPv4 的外部网卡的 ip 来返回。
和我系统设置里的 ip 一样:

此外,我们还通过 os.homename、os.platform、os.arch 分别拿到了主机名、操作系统、操作系统架构等信息。