Skip to content

Node.js 是什么与安装

Node.js 不是一门新语言。它做的事情是把 JavaScript 从浏览器里拿出来,放到操作系统上执行。浏览器里的 JavaScript 操作 DOM、处理用户交互;Node.js 里的 JavaScript 读写文件、监听端口、调用系统命令、操作进程——这些能力来自 libuv 和系统调用绑定,不是 JavaScript 语言本身。

运行时 = 引擎 + 系统能力

组成部分做了什么
JavaScript(ECMAScript)语言本身:变量、函数、类型、原型链、异步模型
V8Google 开发的 JavaScript 引擎,负责把 JS 代码编译成机器码执行
libuv跨平台的异步 I/O 库,提供事件循环、线程池、文件系统、网络、子进程等能力
Node.js 内置模块fshttppathchild_process 等,是 C++ 层面对 libuv 和系统调用的封装
npmNode.js 生态的包管理工具,随 Node.js 一起分发

把这个关系理解清楚,后面排查"为什么同一个 JS 代码浏览器里能跑 Node.js 里报错"会容易很多——报错通常是因为浏览器有 DOM API,Node.js 有 fs/process,它们的全局对象不同。反过来 Node.js 代码在浏览器跑不了,也一样。

JavaScript 语言层面由 TC39 委员会推动年度新版本(ES2015 ~ ES2025),V8 引擎跟进实现,Node.js 再按照发行节奏把新版 V8 整合进各版本。一个语法特性从提案到能在 Node.js LTS 里稳定使用,中间隔了 TC39 推动、V8 实现、Node.js 整合三道环节。

运维场景里的适用边界

Node.js 在运维里更适合这些场景:

合适具体例子
CLI 工具批量服务检查、配置生成、调用发布 API、跑巡检
轻量 API 和 webhook内部回调接收、运维接口、状态查询
构建和自动化脚本前端项目构建、文档站点生成、静态资源处理
JSON/YAML/API 数据处理读配置、调接口、生成报告——JS 原生支持 JSON
简单 Web 面板只读状态页、内部管理页

不太合适的情况:

场景原因
大量 CPU 密集计算Node.js 主线程只有一个,CPU 密集型任务会把事件循环堵住,接口延迟跟着抖动
需要多线程共享内存的复杂计算worker_threads 能做但不顺手,不如 Go/Rust/Java
几行 shell 就能完成的一次性任务为少量逻辑拉 npm 项目、写 package.json 的维护成本偏高
对启动体积极度敏感的嵌入式场景Node.js 运行时本身比可执行文件大很多

不是不能做,而是运维里选择一个工具,首先看的是:维护这堆代码的人以后还愿不愿意看。一个 systemctl 管道就能查的事情,变成 10 个 npm 依赖 + commander + axios + chalk,长期维护成本会累积。

LTS 和版本策略

Node.js 版本分为 LTS(Long Term Support)和 Current,每半年发布一个 Current,偶数大版本进入 LTS:

  • Current:偶数号进入 LTS,奇数号只在 Current 存活。试新特性用 Current,但过了窗口就不再维护
  • LTS:维护 30 个月,持续接收安全补丁和 critical bugfix。生产服务必须跟 LTS
  • EOL:不再维护,建议升级

实际运维中固定大版本号很重要。服务器上和开发机上一个 LTS 20,一个 Current 22——过了两个月重新部署时可能发现 npm 包需要的新语法 Current 有而 LTS 没有,或者原生依赖在新版本 V8 上编译不过。

在 package.json 里声明:

json
{
  "engines": {
    "node": ">=20"
  }
}

这个字段只是声明意图,不是运行时强制校验。真正部署前还是要跑 node -v

bash
node -v      # 看版本
npm -v       # npm 版本随 Node.js 变化
which node   # 确认执行的到底是哪个路径下的 node
node -p "process.platform"   # 看系统平台
node -p "process.arch"       # 看 CPU 架构

二进制安装(服务器首选)

服务器上用官方二进制包安装,比用系统源更可控:版本明确、路径清楚、切换和回滚都只需要改软链接。

bash
# 1. 从镜像站拉指定版本的二进制包
curl -L https://mirrors.aliyun.com/nodejs-release/v20.18.0/node-v20.18.0-linux-x64.tar.xz \
  -o node-v20.18.0-linux-x64.tar.xz

# 2. 解压到 /usr/local,版本号直接写进目录名
tar -xf node-v20.18.0-linux-x64.tar.xz -C /usr/local/

# 3. 建一个不带版本号的软链接——以后切版本只改这个链接
ln -snf /usr/local/node-v20.18.0-linux-x64 /usr/local/node

# 4. PATH 写进 profile.d
cat > /etc/profile.d/node.sh <<'EOF'
export PATH=/usr/local/node/bin:$PATH
EOF

source /etc/profile.d/node.sh

验证:

bash
node -v
npm -v
which node
node -p "process.execPath"

process.execPath 能看到 Node.js 二进制文件的完整路径。机器上同时装了系统源的旧 node 和 /usr/local 的新 node,PATH 顺序错了就执行了错的版本——which nodeprocess.execPath 一起看能定位。

系统包管理器安装

bash
# Ubuntu / Debian
apt install nodejs npm -y

# RHEL / Rocky / CentOS
yum install nodejs npm -y

最省事但版本可能偏旧。适合临时工具和一次性脚本,长期服务不太适合。系统仓库的 Node.js 优先级低于系统厂商需求,通常不是最新 LTS。

开发机版本管理

开发机一台机器上经常同时维护多个项目,不同项目要求不同 Node.js 版本。用版本管理工具可以随时切换:

  • fnm(Fast Node Manager):Rust 实现,跨平台,速度快,Windows 支持好
  • volta:Rust 实现,项目级固定版本,自动切换
  • nvm:最老的方案,bash 脚本,启动时可能影响终端性能
bash
fnm install 20
fnm use 20
fnm list
node -v

服务器上不需要版本管理工具。systemd service 里 ExecStart 写绝对路径 /usr/local/node/bin/node,不依赖用户 shell 的 PATH。

容器里使用

dockerfile
FROM node:20-bookworm-slim

WORKDIR /app

# 先拷 package 文件,利用 Docker 层缓存——代码改了依赖没变时不用重装
COPY package*.json ./
RUN npm ci --omit=dev

COPY . .

CMD ["node", "server.js"]

关于镜像选择:

  • node:20-slim:基于 Debian,体积比完整版小,多数场景合适
  • node:20-alpine:基于 Alpine(musl libc),更小,但用 sharpbcryptsqlite3 这类原生 C++ 依赖时可能因为 musl/glibc 差异编译不过。出现这类问题时不是 Node.js 的问题,是原生模块和 libc 的兼容问题

一个最小 HTTP 服务——验证安装

装好以后用一个内置 http 模块的服务确认整个链路通——不只是 node 命令能执行,而是网络监听、端口绑定、HTTP 响应都正常:

js
// server.js
import http from "node:http";

const server = http.createServer((req, res) => {
  // 运维场景里健康检查端点几乎每个服务都要有
  if (req.url === "/healthz") {
    res.writeHead(200, { "content-type": "application/json" });
    res.end(JSON.stringify({ ok: true }));
    return;
  }

  res.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
  res.end("hello from node\n");
});

server.listen(3000, "0.0.0.0", () => {
  // 0.0.0.0 绑定所有网卡——容器里 localhost 只绑容器内部,外部访问不到
  console.log("listening on port 3000");
});
bash
node server.js &
curl http://127.0.0.1:3000/
curl http://127.0.0.1:3000/healthz

先确认运行时时和最简单的 HTTP 都正常,再装框架和拆模块。出问题时退回这个最小版本验证,能排除框架、依赖和业务逻辑的干扰。