人人都会AI编程

8.3 进阶 Hooks

更新时间:2026-07-10

在开发服务端应用时,经常需要获取运行环境的底层信息,比如监控服务器的 CPU 负载、查看剩余内存、判断当前操作系统类型、或者获取本机的 IP 地址。Node.js 的 os 模块提供了这一系列与操作系统交互的 API,无需安装任何第三方包即可使用。

os 模块的方法虽然不多,但覆盖了日常开发中绝大部分对系统信息的需求。本节将重点讲解 CPU、内存、操作系统类型和网络接口四个最常用的信息获取方式,并穿插实际应用场景,帮助你快速将这些能力集成到自己的项目中。

8.3.1 CPU 信息获取

os.cpus() 方法会返回一个数组,数组中的每个元素对应一个逻辑 CPU 核心(包括超线程)。每个核心的信息对象包含以下字段:

  • model:CPU 型号字符串
  • speed:CPU 主频,单位 MHz
  • times:一个对象,包含 usernicesysidleirq 等数字,分别代表该核心在不同状态下花费的时间(单位毫秒,但受系统时钟限制,不一定精确)

简单调用示例如下:

const os = require('os');

const cpus = os.cpus();
console.log(`逻辑 CPU 核心数:${cpus.length}`);

cpus.forEach((cpu, index) => {
  console.log(`核心 ${index}: ${cpu.model} @ ${cpu.speed} MHz`);
});

在一台 4 核 8 线程的 i7 机器上,可能会输出类似:

逻辑 CPU 核心数:8
核心 0: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz @ 1992 MHz
核心 1: ...

CPU 信息的实用价值:

  • 负载判断与集群策略cluster 模块通常会根据 os.cpus().length 决定启动多少个子进程。我们也可以利用 times 字段计算出单个核心的空闲率(idle / total),再配合 os.loadavg() 判断整体负载,从而决定是否需要动态扩容。
  • 监控与告警:搭建自己的监控面板时,把每个核心的型号、主频、以及实时计算的利用率暴露出去,便于运维人员掌握服务器状态。
  • 区分开发和生产环境:偶尔可能会根据 CPU 型号做软性判断,但一般不推荐硬编码逻辑,更建议用环境变量区分。

需要留意的是,times 对象里的值是累积时间,要计算真实占用率,需要两次采样相减后再做除法。下面是一个简单的 CPU 使用率计算示例:

function getCPUUsage(sampleTime = 1000) {
  return new Promise(resolve => {
    const start = os.cpus().map(cpu => cpu.times);
    setTimeout(() => {
      const end = os.cpus().map(cpu => cpu.times);
      const usage = end.map((core, i) => {
        const totalStart = Object.values(start[i]).reduce((a, b) => a + b, 0);
        const totalEnd = Object.values(core).reduce((a, b) => a + b, 0);
        const idleDiff = core.idle - start[i].idle;
        return 1 - idleDiff / (totalEnd - totalStart);
      });
      resolve(usage);
    }, sampleTime);
  });
}

getCPUUsage().then(console.log); // [0.05, 0.12, ...] 各核心使用率

8.3.2 内存信息获取

内存信息通过两个方法获取:

  • os.totalmem():以字节为单位返回系统总内存容量
  • os.freemem():以字节为单位返回当前空闲内存容量

两者返回的都是整数,需要手动转换为更可读的单位:

const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;

function formatBytes(bytes) {
  const units = ['B', 'KB', 'MB', 'GB', 'TB'];
  let value = bytes;
  let unitIndex = 0;
  while (value >= 1024 && unitIndex < units.length - 1) {
    value /= 1024;
    unitIndex++;
  }
  return `${value.toFixed(2)} ${units[unitIndex]}`;
}

console.log(`总内存:${formatBytes(totalMem)}`);
console.log(`空闲内存:${formatBytes(freeMem)}`);
console.log(`已用内存:${formatBytes(usedMem)}`);
console.log(`使用率:${((usedMem / totalMem) * 100).toFixed(2)}%`);

应用场景:

  • 健康检查端点:很多服务会在 /health 接口中返回当前内存使用比例,当超过阈值(如 90%)时,负载均衡器可以暂时停止向该节点转发流量。
  • 内存溢出预警:配合进程监控工具(如 PM2),当系统可用内存持续下降时主动记录日志或发送告警,帮助排查是否有内存泄漏。
  • 自动降级策略:内存紧张时,可主动清理缓存、拒绝大文件上传等,防止进程 OOM 崩溃。

os.freemem() 返回的是系统全局的空闲内存,并不是 Node.js 进程自身的内存占用。后者可以通过 process.memoryUsage() 获得,两者结合可以更全面地评估资源状况。

8.3.3 操作系统信息

os 模块提供了一系列方法用于获取操作系统的类型、版本和架构:

  • os.type():返回操作系统类型,如 'Linux''Darwin'(macOS)、'Windows_NT'
  • os.platform():返回更具体的平台标识,如 'linux''darwin''win32',常用于跨平台工具的条件判断
  • os.release():返回操作系统的发行版本号,例如 '5.4.0-66-generic'
  • os.arch():返回 CPU 架构,如 'x64''arm''arm64'
  • os.version():返回内核版本字符串(Linux 下更详细)
  • os.hostname():返回主机名
  • os.homedir():返回当前用户的主目录路径
  • os.tmpdir():返回系统临时文件夹路径

综合使用示例:

console.log('操作系统类型:', os.type());
console.log('平台标识:', os.platform());
console.log('发行版本:', os.release());
console.log('CPU 架构:', os.arch());
console.log('主机名:', os.hostname());
console.log('用户目录:', os.homedir());
console.log('临时目录:', os.tmpdir());

实际开发中的用途:

  • 跨平台兼容:在用 Node.js 编写 CLI 工具或构建脚本时,经常要根据 os.platform() 来决定执行的命令(如 Windows 上用 copy,UNIX 上用 cp)或路径分隔符。
  • 条件加载原生模块:有些 npm 包会依据 os.arch() 选择不同的预编译二进制文件。
  • 环境检测与日志记录:在启动应用时打印操作系统信息,方便日后回溯问题时确定运行环境。
  • 路径拼接:虽然 path 模块会处理平台差异,但在手动处理配置路径时,os.homedir() 常用于指向用户主目录下的配置文件(如 ~/.myapp/config.json)。

注意:os.type()os.platform() 同名方法的区别在 Node.js 早期版本中可能存在细微差异,现在基本可以互换,但 platform() 的值更简洁,通常推荐用它做逻辑判断。

8.3.4 网络接口信息

在多网卡服务器、容器环境或者需要获取本机 IP 的场景中,os.networkInterfaces() 是极为常用的方法。它返回一个对象,键是网络接口名称(如 'lo''eth0''WLAN'),值是一个数组,因为一个接口可能同时绑定 IPv4 和 IPv6 地址。

每个地址项包含:

  • address:IP 地址(IPv4 或 IPv6)
  • netmask:子网掩码
  • family'IPv4''IPv6'
  • mac:MAC 地址(物理地址)
  • internal:布尔值,true 表示是内部回环接口(如 127.0.0.1
  • cidr:CIDR 表示法(Node.js v18 以上)
  • scopeid:IPv6 的作用域 ID

获取所有网络信息:

const interfaces = os.networkInterfaces();
console.log(interfaces);

通常我们更关心如何获取本机的局域网 IP(IPv4),用于在其他服务中注册自己的地址。一个常见的工具函数如下:

function getLocalIP() {
  const interfaces = os.networkInterfaces();
  for (const name of Object.keys(interfaces)) {
    for (const iface of interfaces[name]) {
      // 跳过内部地址和非 IPv4
      if (iface.family === 'IPv4' && !iface.internal) {
        return iface.address;
      }
    }
  }
  return '127.0.0.1';
}

console.log('本机 IP:', getLocalIP());

在 Docker 容器内,网络接口名称通常类似 eth0,也可能有多个虚拟网卡。上述函数会返回第一个非内部的 IPv4 地址,基本满足需求。如果有更复杂的需求(如根据网卡名称或特定子网筛选),可以在循环中加入额外判断。

真实应用场景:

  • 服务注册与发现:微服务启动时,向注册中心(如 Consul、Eureka)上报自己的 IP 和端口。os.networkInterfaces() 可以动态拿到真实 IP,无需硬编码。
  • 健康检查:暴露服务状态时附带本机 IP,便于运维工具区分不同节点。
  • 调试信息:在启动日志中打印本机 IP,方便本地联调时快速复制地址给前端或其他同事。
  • 安全控制:有的内网服务可能只允许指定网段的请求,通过检查请求来源 IP 是否在某个 netmask 范围内做权限控制(需手动计算,或结合 ip 等库)。

注意:os.networkInterfaces() 每次调用都会重新获取信息,开销很小但也不是完全免费,如果频繁调用(如每次请求都获取 IP)建议缓存结果。另外,在多 IP 环境中要明确业务需要哪个 IP(如内网通信用 eth0,服务对外暴露可能用公网地址),必要时可通过环境变量强制指定。

8.3.5 其他实用方法

os 模块还提供了一些辅助方法,虽然使用频率稍低,但在特定场景中非常方便:

  • os.uptime():返回系统运行时间(秒),可用于计算服务启动了多久,或者定期打印系统 uptime 到监控。
  • os.loadavg():返回一个包含 1、5、15 分钟平均负载的数组(仅 UNIX/Linux 和 macOS 下有意义,Windows 上会返回 [0, 0, 0])。负载是系统压力最直接的指标,当 1 分钟负载接近 CPU 核心数时,说明系统已经满负荷。
  • os.userInfo():返回当前用户的信息对象,包含 usernameuidgidshellhomedir 等。可用于获取当前启动用户的身份,或配置个性化的路径。
  • os.endianness():返回 CPU 字节序,'BE''LE'。处理二进制协议时可能需要用到,但现代桌面和服务器端基本全都是小端序(LE)。
  • os.EOL:返回操作系统的默认行末符(\n\r\n),在写文件或日志时很有用,确保在不同平台上用正确的换行。

综合运用这些方法,可以很方便地构建一个系统信息摘要:

const info = {
  platform: os.platform(),
  hostname: os.hostname(),
  arch: os.arch(),
  cpus: os.cpus().length,
  memory: {
    total: formatBytes(os.totalmem()),
    free: formatBytes(os.freemem()),
  },
  uptime: `${Math.floor(os.uptime() / 3600)} 小时`,
  loadavg: os.loadavg(),
  network: getLocalIP(),
};
console.log(info);

这个摘要可以直接作为健康检查接口的响应体,帮助运维快速了解当前实例的状态。

8.3.6 实际应用:一个简单的系统健康检查接口

将上述所有技能结合在一起,我们可以为任何 Node.js 服务快速添加一个轻量级的健康检查端点。以下是一个基于 Express 的示例:

const express = require('express');
const os = require('os');

const app = express();

function getHealthStatus() {
  const usedMemRatio = 1 - os.freemem() / os.totalmem();
  const cpuUsage = os.loadavg()[0] / os.cpus().length; // 1分钟负载 / 核心数
  const status = (usedMemRatio < 0.9 && cpuUsage < 0.7) ? 'ok' : 'warning';

  return {
    status,
    timestamp: new Date().toISOString(),
    hostname: os.hostname(),
    uptime: os.uptime(),
    cpu: {
      cores: os.cpus().length,
      loadAvg1m: os.loadavg()[0],
      usageRatio: cpuUsage.toFixed(2),
    },
    memory: {
      total: os.totalmem(),
      free: os.freemem(),
      usageRatio: usedMemRatio.toFixed(2),
    },
    network: os.networkInterfaces()['eth0']?.[0]?.address || '未知',
  };
}

app.get('/health', (req, res) => {
  res.json(getHealthStatus());
});

app.listen(3000, () => console.log('Health check on port 3000'));

这样,当负载均衡器或 K8s 探活访问 /health 时,就能获得实时的 CPU、内存、网络和运行时长信息,并判断节点是否健康。真实环境下还可以加入数据库连接检查、磁盘剩余空间等更多指标。

os 模块以极轻量的方式提供了系统底层信息的访问能力,是构建 DevOps 工具、运维脚本、服务监控的基础。它的 API 简洁可靠,无需任何第三方依赖,在性能敏感、追求启动速度的场景下尤其适用。在后续的章节中,我们将继续探讨与进程管理和集群模式相关的模块,这些模块常常需要依赖 os 获取的信息来做智能调度。