55.Node.js 技术指南
目录
点击展开目录
Node.js 概述
什么是Node.js
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,它使 JavaScript 能够脱离浏览器在服务器端运行。Node.js 采用事件驱动、非阻塞 I/O 模型,使其轻量且高效,非常适合构建数据密集型的实时应用程序。
核心定义要点:
| 特性 | 说明 |
|---|---|
| 运行时环境 | 不是编程语言,而是让 JavaScript 运行在服务端的平台 |
| V8 引擎 | 使用 Google 开发的高性能 JavaScript 引擎 |
| 事件驱动 | 基于事件循环机制处理并发请求 |
| 非阻塞 I/O | 异步处理 I/O 操作,不会阻塞主线程 |
| 单线程 | 主线程单线程执行,通过事件循环实现高并发 |
Node.js 的本质理解:
Node.js 的出现打破了 JavaScript 只能在浏览器中运行的限制,使得前后端可以使用同一种语言开发,大大提高了开发效率和代码复用性。
Node.js发展历史
重要里程碑:
| 时间 | 事件 | 意义 |
|---|---|---|
| 2009年 | Ryan Dahl 发布 Node.js | 开创服务端 JavaScript 时代 |
| 2010年 | npm 包管理器发布 | 建立生态系统基础 |
| 2011年 | Windows 版本发布 | 跨平台支持 |
| 2014年 | io.js 分支出现 | 社区分裂与技术演进 |
| 2015年 | Node.js 基金会成立 | io.js 合并,统一发展 |
| 2015年 | Node.js 4.0 发布 | 支持 ES6 特性 |
| 2017年 | Node.js 8.0 发布 | 支持 async/await |
| 2019年 | Node.js 12.0 发布 | 支持 ES Modules |
| 2021年 | Node.js 16.0 发布 | V8 9.0,性能大幅提升 |
| 2023年 | Node.js 20.0 发布 | 稳定的测试运行器、权限模型 |
| 2024年 | Node.js 22.0 发布 | 原生 WebSocket 客户端支持 |
版本发布策略:
Node.js 采用偶数版本为 LTS(长期支持)版本的策略:
- Current 版本:最新特性,适合尝鲜
- Active LTS:积极维护,推荐生产使用
- Maintenance LTS:仅修复关键 bug 和安全问题
Node.js特点与优势
核心特点:
1. 事件驱动与非阻塞 I/O
Node.js 的核心优势在于其事件驱动的非阻塞 I/O 模型。当执行 I/O 操作(如读取文件、数据库查询、网络请求)时,Node.js 不会等待操作完成,而是继续执行后续代码,当 I/O 操作完成后通过回调函数处理结果。
阻塞 vs 非阻塞对比:
| 模型 | 处理方式 | 资源利用 | 适用场景 |
|---|---|---|---|
| 阻塞 I/O | 等待操作完成才继续 | 线程空闲等待,资源浪费 | 简单同步逻辑 |
| 非阻塞 I/O | 发起操作后立即返回 | 充分利用 CPU 时间 | 高并发 I/O 密集型 |
2. 单线程优势
- 无需处理多线程同步问题:避免死锁、竞态条件
- 内存占用低:每个连接不需要新建线程
- 上下文切换开销小:没有线程切换成本
3. 跨平台支持
Node.js 通过 libuv 库实现跨平台,支持:
- Windows
- macOS
- Linux
- 各种 Unix 系统
4. 丰富的生态系统
npm 是世界上最大的开源库生态系统,拥有超过 200 万个包,几乎涵盖所有开发需求。
Node.js应用场景
最适合的场景:
| 场景类型 | 具体应用 | 原因 |
|---|---|---|
| 实时应用 | 聊天室、在线游戏、协作工具 | WebSocket 支持好,事件驱动 |
| API 服务 | RESTful API、GraphQL | 高并发处理能力 |
| 微服务 | 服务拆分、API 网关 | 轻量、启动快 |
| 流处理 | 视频流、文件上传下载 | Stream API 强大 |
| 服务端渲染 | SSR、同构应用 | 前后端统一技术栈 |
| 工具开发 | CLI 工具、构建工具 | npm 生态丰富 |
| IoT 应用 | 物联网网关、数据采集 | 轻量、事件驱动 |
不太适合的场景:
| 场景 | 原因 | 替代方案 |
|---|---|---|
| CPU 密集型计算 | 单线程会阻塞事件循环 | Go、Rust、C++ |
| 复杂事务处理 | 缺乏成熟的事务框架 | Java Spring |
| 大型单体应用 | 类型系统较弱 | TypeScript + NestJS |
典型应用架构:
Node.js"] D["业务服务1
Node.js"] E["业务服务2
Node.js"] F["实时服务
WebSocket"] end subgraph "数据层" G["MySQL"] H["MongoDB"] I["Redis"] end A --> C B --> C C --> D C --> E C --> F D --> G D --> I E --> H E --> I F --> I style C fill:#4fc3f7,stroke:#333 style D fill:#81c784,stroke:#333 style E fill:#81c784,stroke:#333 style F fill:#ffb74d,stroke:#333
Node.js与其他技术对比
Node.js vs 其他后端技术:
| 对比维度 | Node.js | Java | Python | Go |
|---|---|---|---|---|
| 并发模型 | 事件驱动单线程 | 多线程 | 多线程/协程 | 协程(goroutine) |
| 性能 | I/O密集型优秀 | 全面均衡 | 相对较慢 | 高性能 |
| 学习曲线 | 低(前端转型容易) | 较高 | 低 | 中等 |
| 生态系统 | npm 最丰富 | Maven 成熟 | pip 丰富 | 相对较少 |
| 类型系统 | 弱类型(可用TS) | 强类型 | 弱类型 | 强类型 |
| 内存占用 | 较低 | 较高 | 中等 | 低 |
| 启动速度 | 快 | 慢 | 中等 | 快 |
| 适用场景 | API、实时应用 | 企业级应用 | AI/ML、脚本 | 云原生、高性能 |
选型建议:
- 选择 Node.js:I/O 密集型、实时应用、前后端统一技术栈、快速迭代
- 选择 Java:大型企业应用、复杂业务逻辑、强类型需求
- 选择 Go:高性能要求、云原生应用、系统编程
- 选择 Python:AI/ML、数据分析、快速原型
JavaScript语法基础
Node.js 使用 JavaScript 作为编程语言,掌握 JavaScript 语法是学习 Node.js 的基础。本节介绍 JavaScript 核心语法和 ES6+ 新特性。
变量与数据类型
变量声明方式:
| 关键字 | 作用域 | 可重新赋值 | 可重复声明 | 提升行为 |
|---|---|---|---|---|
| var | 函数作用域 | ✅ | ✅ | 变量提升(值为 undefined) |
| let | 块级作用域 | ✅ | ❌ | 暂时性死区(TDZ) |
| const | 块级作用域 | ❌ | ❌ | 暂时性死区(TDZ) |
使用建议:
- 优先使用 const:不需要重新赋值的变量
- 需要重新赋值时用 let:循环变量、累加器等
- 避免使用 var:容易造成变量污染
数据类型:
JavaScript 有 8 种数据类型,分为原始类型和引用类型:
| 类型 | 分类 | 示例 | typeof 结果 |
|---|---|---|---|
| undefined | 原始类型 | undefined | “undefined” |
| null | 原始类型 | null | “object”(历史遗留) |
| boolean | 原始类型 | true, false | “boolean” |
| number | 原始类型 | 42, 3.14, NaN, Infinity | “number” |
| bigint | 原始类型 | 9007199254740991n | “bigint” |
| string | 原始类型 | 'hello', "world" | “string” |
| symbol | 原始类型 | Symbol('id') | “symbol” |
| object | 引用类型 | {}, [], function(){} | “object”/“function” |
类型判断方法:
// typeof - 适合判断原始类型
typeof 'hello' // 'string'
typeof 42 // 'number'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof Symbol() // 'symbol'
typeof 10n // 'bigint'
// instanceof - 判断对象类型
[] instanceof Array // true
{} instanceof Object // true
new Date() instanceof Date // true
// Object.prototype.toString - 最准确的方法
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
// Array.isArray - 判断数组
Array.isArray([]) // true
Array.isArray({}) // false
类型转换:
| 转换目标 | 方法 | 示例 |
|---|---|---|
| 转字符串 | String(), .toString(), + '' | String(123) → '123' |
| 转数字 | Number(), parseInt(), parseFloat(), + | Number('42') → 42 |
| 转布尔 | Boolean(), !! | Boolean('') → false |
假值(Falsy)列表:
// 以下值转为布尔值都是 false
false, 0, -0, 0n, '', null, undefined, NaN
函数与作用域
函数定义方式:
| 方式 | 语法 | 特点 |
|---|---|---|
| 函数声明 | function fn() {} | 会提升,有 this |
| 函数表达式 | const fn = function() {} | 不提升,有 this |
| 箭头函数 | const fn = () => {} | 不提升,无自己的 this |
| 方法简写 | { fn() {} } | 对象方法,有 this |
箭头函数特点:
| 特点 | 说明 |
|---|---|
| 没有自己的 this | 继承外层作用域的 this |
| 没有 arguments | 使用剩余参数 ...args |
| 不能作为构造函数 | 不能使用 new |
| 没有 prototype | 不能定义原型方法 |
| 简洁语法 | 单表达式可省略 return |
// 箭头函数简写
const add = (a, b) => a + b; // 单表达式,省略 return
const square = x => x * x; // 单参数,省略括号
const getObj = () => ({ name: 'test' }); // 返回对象需要括号
作用域类型:
Global Scope"] B["函数作用域
Function Scope"] C["块级作用域
Block Scope"] end A --> B B --> C style A fill:#f44336,stroke:#333,color:#fff style B fill:#ff9800,stroke:#333 style C fill:#4caf50,stroke:#333,color:#fff
闭包(Closure):
闭包是指函数能够访问其词法作用域外的变量,即使函数在其词法作用域外执行。
function createCounter() {
let count = 0; // 私有变量
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.getCount(); // 2
闭包应用场景:
| 场景 | 说明 |
|---|---|
| 数据私有化 | 模拟私有变量 |
| 函数工厂 | 创建定制函数 |
| 回调函数 | 保持上下文 |
| 模块模式 | 封装模块 |
this 指向规则:
| 调用方式 | this 指向 | 示例 |
|---|---|---|
| 普通函数调用 | 全局对象(严格模式为 undefined) | fn() |
| 方法调用 | 调用对象 | obj.fn() |
| 构造函数调用 | 新创建的实例 | new Fn() |
| call/apply/bind | 指定的对象 | fn.call(obj) |
| 箭头函数 | 外层作用域的 this | () => {} |
对象与数组
对象操作:
// 对象创建
const obj = { name: 'Node.js', version: 20 };
// 属性访问
obj.name // 点语法
obj['name'] // 方括号语法(支持动态属性名)
// 属性操作
obj.author = 'Ryan Dahl'; // 添加属性
delete obj.version; // 删除属性
'name' in obj; // 检查属性存在
// 对象方法
Object.keys(obj) // 获取所有键
Object.values(obj) // 获取所有值
Object.entries(obj) // 获取键值对数组
Object.assign({}, obj) // 浅拷贝
Object.freeze(obj) // 冻结对象
Object.seal(obj) // 密封对象
对象解构:
const user = { name: 'John', age: 25, city: 'NYC' };
// 基本解构
const { name, age } = user;
// 重命名
const { name: userName } = user;
// 默认值
const { country = 'USA' } = user;
// 嵌套解构
const { address: { street } } = { address: { street: '123 Main' } };
// 剩余属性
const { name, ...rest } = user; // rest = { age: 25, city: 'NYC' }
数组操作:
| 方法 | 说明 | 是否修改原数组 |
|---|---|---|
| push/pop | 末尾添加/删除 | ✅ |
| unshift/shift | 开头添加/删除 | ✅ |
| splice | 任意位置增删改 | ✅ |
| slice | 截取子数组 | ❌ |
| concat | 合并数组 | ❌ |
| map | 映射转换 | ❌ |
| filter | 过滤 | ❌ |
| reduce | 归约 | ❌ |
| find/findIndex | 查找元素/索引 | ❌ |
| some/every | 部分/全部满足 | ❌ |
| includes | 是否包含 | ❌ |
| flat/flatMap | 扁平化 | ❌ |
| sort | 排序 | ✅ |
| reverse | 反转 | ✅ |
数组高阶函数:
const numbers = [1, 2, 3, 4, 5];
// map - 映射
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
// filter - 过滤
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
// reduce - 归约
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15
// find - 查找
const found = numbers.find(n => n > 3); // 4
// some/every - 条件判断
numbers.some(n => n > 4); // true
numbers.every(n => n > 0); // true
// 链式调用
const result = numbers
.filter(n => n % 2 === 0)
.map(n => n * 2)
.reduce((acc, n) => acc + n, 0); // 12
数组解构:
const arr = [1, 2, 3, 4, 5];
// 基本解构
const [first, second] = arr; // 1, 2
// 跳过元素
const [, , third] = arr; // 3
// 剩余元素
const [head, ...tail] = arr; // 1, [2, 3, 4, 5]
// 默认值
const [a, b, c = 0] = [1, 2]; // 1, 2, 0
// 交换变量
let x = 1, y = 2;
[x, y] = [y, x]; // x = 2, y = 1
ES6+新特性
模板字符串:
const name = 'Node.js';
const version = 20;
// 字符串插值
const msg = `Welcome to ${name} v${version}`;
// 多行字符串
const html = `
<div>
<h1>${name}</h1>
<p>Version: ${version}</p>
</div>
`;
// 标签模板
function highlight(strings, ...values) {
return strings.reduce((result, str, i) =>
result + str + (values[i] ? `<mark>${values[i]}</mark>` : ''), '');
}
const highlighted = highlight`Hello ${name}!`;
展开运算符(Spread):
// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
// 对象展开
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
// 函数参数
const nums = [1, 2, 3];
Math.max(...nums); // 3
// 复制数组/对象(浅拷贝)
const arrCopy = [...arr1];
const objCopy = { ...obj1 };
剩余参数(Rest):
// 函数剩余参数
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10
// 解构中的剩余
const [first, ...rest] = [1, 2, 3, 4];
const { a, ...others } = { a: 1, b: 2, c: 3 };
可选链(Optional Chaining):
const user = {
name: 'John',
address: {
city: 'NYC'
}
};
// 安全访问嵌套属性
user?.address?.city // 'NYC'
user?.contact?.email // undefined(不会报错)
// 安全调用方法
user.getName?.() // undefined
// 安全访问数组
const arr = [1, 2, 3];
arr?.[0] // 1
空值合并(Nullish Coalescing):
// ?? 只在 null 或 undefined 时使用默认值
const value1 = null ?? 'default'; // 'default'
const value2 = undefined ?? 'default'; // 'default'
const value3 = 0 ?? 'default'; // 0(0 不是 nullish)
const value4 = '' ?? 'default'; // ''(空字符串不是 nullish)
// 与 || 的区别
0 || 'default' // 'default'(0 是 falsy)
0 ?? 'default' // 0(0 不是 nullish)
Promise 与异步:
// Promise 创建
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('done'), 1000);
});
// Promise 使用
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('完成'));
// async/await
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('请求失败:', error);
}
}
// 并行执行
const [user, posts] = await Promise.all([
fetchUser(),
fetchPosts()
]);
迭代器与生成器:
// 生成器函数
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = numberGenerator();
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: 3, done: false }
gen.next(); // { value: undefined, done: true }
// 异步生成器
async function* asyncGenerator() {
yield await fetchData1();
yield await fetchData2();
}
// for await...of
for await (const data of asyncGenerator()) {
console.log(data);
}
类与面向对象
类定义:
class Animal {
// 私有字段(ES2022)
#privateField = 'private';
// 静态属性
static kingdom = 'Animalia';
// 构造函数
constructor(name) {
this.name = name;
}
// 实例方法
speak() {
console.log(`${this.name} makes a sound`);
}
// 静态方法
static create(name) {
return new Animal(name);
}
// Getter
get info() {
return `Animal: ${this.name}`;
}
// Setter
set nickname(value) {
this._nickname = value;
}
}
类继承:
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
// 方法重写
speak() {
console.log(`${this.name} barks`);
}
// 调用父类方法
parentSpeak() {
super.speak();
}
}
const dog = new Dog('Buddy', 'Golden Retriever');
dog.speak(); // 'Buddy barks'
类特性对比:
| 特性 | ES5 构造函数 | ES6 类 |
|---|---|---|
| 语法 | function Fn() {} | class Fn {} |
| 继承 | 原型链 + call | extends + super |
| 静态方法 | Fn.method = ... | static method() |
| 私有属性 | 闭包模拟 | #field |
| Getter/Setter | Object.defineProperty | get/set |
Mixin 模式:
// 定义 Mixin
const Flyable = {
fly() {
console.log(`${this.name} is flying`);
}
};
const Swimmable = {
swim() {
console.log(`${this.name} is swimming`);
}
};
// 应用 Mixin
class Duck extends Animal {}
Object.assign(Duck.prototype, Flyable, Swimmable);
const duck = new Duck('Donald');
duck.fly(); // 'Donald is flying'
duck.swim(); // 'Donald is swimming'
常用设计模式:
| 模式 | 说明 | 应用场景 |
|---|---|---|
| 单例模式 | 确保只有一个实例 | 配置管理、数据库连接 |
| 工厂模式 | 创建对象的接口 | 对象创建逻辑复杂时 |
| 观察者模式 | 发布-订阅 | 事件系统、状态管理 |
| 策略模式 | 封装算法 | 多种算法切换 |
| 装饰器模式 | 动态添加功能 | 功能增强 |
// 单例模式
class Database {
static #instance;
constructor() {
if (Database.#instance) {
return Database.#instance;
}
Database.#instance = this;
}
static getInstance() {
if (!Database.#instance) {
Database.#instance = new Database();
}
return Database.#instance;
}
}
// 观察者模式
class EventEmitter {
#events = {};
on(event, listener) {
if (!this.#events[event]) {
this.#events[event] = [];
}
this.#events[event].push(listener);
}
emit(event, ...args) {
if (this.#events[event]) {
this.#events[event].forEach(listener => listener(...args));
}
}
}
Node.js 核心架构
V8引擎
V8 是 Google 开发的高性能 JavaScript 和 WebAssembly 引擎,用 C++ 编写,是 Node.js 的核心组件之一。
V8 引擎核心特性:
| 特性 | 说明 |
|---|---|
| JIT 编译 | 即时编译,将 JavaScript 直接编译为机器码 |
| 隐藏类 | 优化对象属性访问速度 |
| 内联缓存 | 缓存方法调用,提高执行效率 |
| 垃圾回收 | 分代式垃圾回收,自动内存管理 |
| 优化编译器 | TurboFan 优化热点代码 |
V8 执行流程:
源代码"] --> B["Parser
解析器"] B --> C["AST
抽象语法树"] C --> D["Ignition
解释器"] D --> E["字节码"] E --> F{"热点代码?"} F -->|是| G["TurboFan
优化编译器"] G --> H["优化机器码"] F -->|否| I["解释执行"] end style A fill:#e3f2fd,stroke:#333 style D fill:#fff9c4,stroke:#333 style G fill:#c8e6c9,stroke:#333 style H fill:#ffccbc,stroke:#333
V8 内存结构:
V8 的内存分为以下几个区域:
| 内存区域 | 用途 | 特点 |
|---|---|---|
| 新生代(New Space) | 存放新创建的对象 | 空间小,GC 频繁 |
| 老生代(Old Space) | 存放存活时间长的对象 | 空间大,GC 较少 |
| 大对象空间(Large Object Space) | 存放超过阈值的大对象 | 不会被移动 |
| 代码空间(Code Space) | 存放 JIT 编译的代码 | 可执行内存 |
| Map 空间 | 存放对象的 Map 信息 | 隐藏类信息 |
垃圾回收机制:
事件循环机制
事件循环(Event Loop)是 Node.js 实现非阻塞 I/O 的核心机制,理解事件循环对于编写高性能 Node.js 应用至关重要。
事件循环的六个阶段:
执行setTimeout/setInterval回调"] --> B["pending callbacks
执行系统操作回调"] B --> C["idle, prepare
内部使用"] C --> D["poll
获取新I/O事件"] D --> E["check
执行setImmediate回调"] E --> F["close callbacks
执行close事件回调"] F --> A end G["process.nextTick"] -.->|每个阶段之间执行| A H["Promise.then"] -.->|微任务队列| A style A fill:#ffcdd2,stroke:#333 style D fill:#c8e6c9,stroke:#333 style E fill:#bbdefb,stroke:#333
各阶段详解:
| 阶段 | 执行内容 | 说明 |
|---|---|---|
| timers | setTimeout、setInterval 回调 | 执行已到期的定时器回调 |
| pending callbacks | 某些系统操作的回调 | 如 TCP 错误回调 |
| idle, prepare | 内部使用 | 仅供 Node.js 内部使用 |
| poll | I/O 回调 | 最重要的阶段,处理 I/O 事件 |
| check | setImmediate 回调 | 在 poll 阶段完成后立即执行 |
| close callbacks | close 事件回调 | 如 socket.on(‘close’) |
微任务与宏任务:
微任务"] C --> D["setTimeout
setInterval"] D --> E["setImmediate"] E --> F["I/O 回调"] end style A fill:#f44336,stroke:#333,color:#fff style B fill:#ff9800,stroke:#333 style C fill:#ffeb3b,stroke:#333 style D fill:#4caf50,stroke:#333,color:#fff style E fill:#2196f3,stroke:#333,color:#fff
执行顺序示例分析:
console.log('1. 同步代码');
setTimeout(() => console.log('2. setTimeout'), 0);
setImmediate(() => console.log('3. setImmediate'));
Promise.resolve().then(() => console.log('4. Promise'));
process.nextTick(() => console.log('5. nextTick'));
console.log('6. 同步代码结束');
// 输出顺序:1 -> 6 -> 5 -> 4 -> 2 -> 3
关键理解点:
- process.nextTick 优先级最高,在当前操作完成后立即执行
- Promise.then 属于微任务,在 nextTick 之后执行
- setTimeout(fn, 0) 和 setImmediate 的执行顺序不确定(在主模块中)
- 在 I/O 回调中,setImmediate 总是先于 setTimeout 执行
libuv库
libuv 是一个跨平台的异步 I/O 库,为 Node.js 提供了事件循环和异步 I/O 的底层实现。
libuv 核心功能:
| 功能模块 | 说明 |
|---|---|
| 事件循环 | 实现 Node.js 的事件循环机制 |
| 异步文件 I/O | 通过线程池实现异步文件操作 |
| 异步网络 I/O | 使用系统原生异步机制(epoll/kqueue/IOCP) |
| 线程池 | 处理无法异步化的操作 |
| 定时器 | 实现 setTimeout/setInterval |
| 子进程 | 进程创建和管理 |
| 信号处理 | Unix 信号处理 |
libuv 架构图:
默认4线程"] E["Network I/O"] F["File I/O"] G["DNS"] end subgraph "操作系统" H["epoll/kqueue/IOCP"] I["文件系统"] J["网络栈"] end A --> B B --> C C --> D C --> E D --> F D --> G E --> H F --> I H --> J style A fill:#e3f2fd,stroke:#333 style C fill:#fff9c4,stroke:#333 style D fill:#c8e6c9,stroke:#333 style H fill:#ffccbc,stroke:#333
线程池工作原理:
libuv 维护一个默认 4 个线程的线程池,用于处理以下操作:
- 文件系统操作(fs 模块)
- DNS 查询(dns.lookup)
- 某些加密操作
- 压缩操作
可以通过环境变量调整线程池大小:
# 设置线程池大小为 8
UV_THREADPOOL_SIZE=8 node app.js
网络 I/O vs 文件 I/O:
| 类型 | 实现方式 | 原因 |
|---|---|---|
| 网络 I/O | 系统原生异步(epoll等) | 操作系统提供异步支持 |
| 文件 I/O | 线程池模拟异步 | 大多数系统不支持真正的异步文件 I/O |
Node.js运行时架构
完整架构图:
fs, http, path, events..."] D["Node.js Bindings
C++ 绑定层"] end subgraph "底层依赖" E["V8 Engine
JavaScript 引擎"] F["libuv
异步 I/O"] G["c-ares
异步 DNS"] H["OpenSSL
加密"] I["zlib
压缩"] J["http-parser
HTTP 解析"] end subgraph "操作系统" K["Linux/Windows/macOS"] end A --> C B --> C C --> D D --> E D --> F D --> G D --> H D --> I D --> J E --> K F --> K style A fill:#e3f2fd,stroke:#333 style C fill:#c8e6c9,stroke:#333 style E fill:#fff9c4,stroke:#333 style F fill:#ffccbc,stroke:#333
各层职责:
| 层级 | 组件 | 职责 |
|---|---|---|
| 应用层 | 用户代码、npm 模块 | 业务逻辑实现 |
| 核心模块层 | fs、http、path 等 | 提供 JavaScript API |
| 绑定层 | C++ Bindings | 连接 JS 和 C++ |
| 底层依赖 | V8、libuv 等 | 提供核心能力 |
| 操作系统 | Linux/Windows/macOS | 系统调用 |
单线程与多进程模型
单线程的理解:
Node.js 的"单线程"指的是执行 JavaScript 代码的主线程是单线程,但 Node.js 本身是多线程的:
执行 JS 代码
事件循环"] subgraph "libuv 线程池" B["Worker 1"] C["Worker 2"] D["Worker 3"] E["Worker 4"] end F["V8 GC 线程"] G["V8 编译线程"] end A --> B A --> C A --> D A --> E style A fill:#f44336,stroke:#333,color:#fff style B fill:#4caf50,stroke:#333,color:#fff style C fill:#4caf50,stroke:#333,color:#fff style D fill:#4caf50,stroke:#333,color:#fff style E fill:#4caf50,stroke:#333,color:#fff
单线程的优缺点:
| 优点 | 缺点 |
|---|---|
| 无需处理线程同步 | CPU 密集型任务会阻塞 |
| 内存占用低 | 无法利用多核 CPU |
| 无死锁问题 | 单点故障风险 |
| 上下文切换开销小 | 错误处理需谨慎 |
多进程解决方案:
为了充分利用多核 CPU,Node.js 提供了多种多进程方案:
1. cluster 模块
管理 Worker"] B["Worker 1
处理请求"] C["Worker 2
处理请求"] D["Worker 3
处理请求"] E["Worker 4
处理请求"] end F["客户端请求"] --> A A -->|负载均衡| B A -->|负载均衡| C A -->|负载均衡| D A -->|负载均衡| E style A fill:#ff9800,stroke:#333 style B fill:#4caf50,stroke:#333,color:#fff style C fill:#4caf50,stroke:#333,color:#fff style D fill:#4caf50,stroke:#333,color:#fff style E fill:#4caf50,stroke:#333,color:#fff
2. child_process 模块
用于创建子进程执行任务:
| 方法 | 用途 | 特点 |
|---|---|---|
| spawn | 执行命令 | 流式输出,适合大量数据 |
| exec | 执行命令 | 缓冲输出,有大小限制 |
| execFile | 执行文件 | 不启动 shell |
| fork | 创建 Node 进程 | 内置 IPC 通道 |
3. worker_threads 模块
Node.js 10+ 引入的真正的多线程支持:
共享内存"] --> A D --> B D --> C style A fill:#2196f3,stroke:#333,color:#fff style B fill:#4caf50,stroke:#333,color:#fff style C fill:#4caf50,stroke:#333,color:#fff style D fill:#ff9800,stroke:#333
多进程方案对比:
| 方案 | 适用场景 | 通信方式 | 内存共享 |
|---|---|---|---|
| cluster | Web 服务器扩展 | IPC | 否 |
| child_process | 执行外部命令/脚本 | IPC/stdio | 否 |
| worker_threads | CPU 密集型计算 | postMessage | SharedArrayBuffer |
Node.js 核心模块
Node.js 提供了丰富的内置模块,无需安装即可使用。这些模块是构建 Node.js 应用的基础。
文件系统模块(fs)
fs 模块提供了与文件系统交互的 API,支持同步和异步两种操作方式。
常用 API 分类:
| 类别 | 同步方法 | 异步方法 | Promise 方法 |
|---|---|---|---|
| 读取文件 | readFileSync | readFile | fs.promises.readFile |
| 写入文件 | writeFileSync | writeFile | fs.promises.writeFile |
| 追加内容 | appendFileSync | appendFile | fs.promises.appendFile |
| 删除文件 | unlinkSync | unlink | fs.promises.unlink |
| 创建目录 | mkdirSync | mkdir | fs.promises.mkdir |
| 读取目录 | readdirSync | readdir | fs.promises.readdir |
| 文件信息 | statSync | stat | fs.promises.stat |
| 重命名 | renameSync | rename | fs.promises.rename |
三种使用方式对比:
// 1. 回调方式(传统)
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// 2. 同步方式(阻塞)
const data = fs.readFileSync('file.txt', 'utf8');
// 3. Promise 方式(推荐)
const data = await fs.promises.readFile('file.txt', 'utf8');
文件操作最佳实践:
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 启动时读取配置 | 同步方式 | 简单,启动时阻塞可接受 |
| 运行时文件操作 | Promise/异步 | 不阻塞事件循环 |
| 大文件处理 | Stream | 内存友好 |
| 批量文件操作 | Promise.all | 并行处理 |
文件监听:
// 监听文件变化
fs.watch('file.txt', (eventType, filename) => {
console.log(`${filename} 发生了 ${eventType} 事件`);
});
// 更精确的监听(轮询方式)
fs.watchFile('file.txt', { interval: 1000 }, (curr, prev) => {
console.log('文件被修改');
});
路径模块(path)
path 模块提供了处理文件和目录路径的工具,跨平台兼容是其核心价值。
核心方法:
| 方法 | 功能 | 示例 |
|---|---|---|
| path.join() | 拼接路径 | path.join('/a', 'b', 'c') → /a/b/c |
| path.resolve() | 解析为绝对路径 | path.resolve('a', 'b') → /当前目录/a/b |
| path.dirname() | 获取目录名 | path.dirname('/a/b/c.js') → /a/b |
| path.basename() | 获取文件名 | path.basename('/a/b/c.js') → c.js |
| path.extname() | 获取扩展名 | path.extname('file.txt') → .txt |
| path.parse() | 解析路径 | 返回 {root, dir, base, ext, name} |
| path.format() | 格式化路径 | 将对象转为路径字符串 |
| path.isAbsolute() | 是否绝对路径 | 返回 boolean |
| path.relative() | 计算相对路径 | 从一个路径到另一个的相对路径 |
跨平台注意事项:
// Windows 和 Unix 路径分隔符不同
path.sep // Windows: '\\' Unix: '/'
// 始终使用 path.join 而不是字符串拼接
// ❌ 错误
const filePath = dir + '/' + filename;
// ✅ 正确
const filePath = path.join(dir, filename);
常用路径变量:
| 变量 | 说明 |
|---|---|
| __dirname | 当前模块所在目录的绝对路径 |
| __filename | 当前模块文件的绝对路径 |
| process.cwd() | 当前工作目录 |
HTTP模块
http 模块是构建 Web 服务器的基础模块。
创建 HTTP 服务器:
请求对象(req)常用属性:
| 属性/方法 | 说明 |
|---|---|
| req.url | 请求 URL |
| req.method | 请求方法(GET/POST等) |
| req.headers | 请求头对象 |
| req.httpVersion | HTTP 版本 |
| req.on(‘data’) | 接收请求体数据 |
| req.on(’end’) | 请求体接收完成 |
响应对象(res)常用方法:
| 方法 | 说明 |
|---|---|
| res.writeHead() | 设置状态码和响应头 |
| res.setHeader() | 设置单个响应头 |
| res.write() | 写入响应体 |
| res.end() | 结束响应 |
| res.statusCode | 设置状态码 |
HTTP 客户端请求:
// 发起 GET 请求
http.get('http://api.example.com/data', (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => console.log(data));
});
// 发起 POST 请求
const req = http.request({
hostname: 'api.example.com',
method: 'POST',
headers: { 'Content-Type': 'application/json' }
}, (res) => {
// 处理响应
});
req.write(JSON.stringify({ key: 'value' }));
req.end();
事件模块(events)
events 模块是 Node.js 事件驱动架构的核心,几乎所有 Node.js 核心模块都继承自 EventEmitter。
EventEmitter 核心方法:
| 方法 | 说明 |
|---|---|
| on(event, listener) | 注册事件监听器 |
| once(event, listener) | 注册一次性监听器 |
| emit(event, …args) | 触发事件 |
| off(event, listener) | 移除监听器 |
| removeAllListeners() | 移除所有监听器 |
| listeners(event) | 获取监听器数组 |
| listenerCount(event) | 获取监听器数量 |
事件机制流程:
emitter.on('event', fn)"] --> B["事件队列
存储监听器"] C["触发事件
emitter.emit('event')"] --> D["遍历执行
所有监听器"] B --> D end style A fill:#e3f2fd,stroke:#333 style C fill:#c8e6c9,stroke:#333 style D fill:#fff9c4,stroke:#333
自定义事件类:
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {
doSomething() {
this.emit('start');
// 执行操作
this.emit('end', { result: 'success' });
}
}
const myEmitter = new MyEmitter();
myEmitter.on('start', () => console.log('开始'));
myEmitter.on('end', (data) => console.log('结束', data));
错误事件处理:
// 必须监听 error 事件,否则会抛出异常
emitter.on('error', (err) => {
console.error('发生错误:', err);
});
流模块(stream)
Stream 是 Node.js 处理流式数据的抽象接口,适合处理大量数据而不占用大量内存。
四种流类型:
| 类型 | 说明 | 示例 |
|---|---|---|
| Readable | 可读流 | fs.createReadStream、http.IncomingMessage |
| Writable | 可写流 | fs.createWriteStream、http.ServerResponse |
| Duplex | 双工流(可读可写) | net.Socket、TCP 连接 |
| Transform | 转换流 | zlib.createGzip、crypto 流 |
流的工作模式:
流的两种模式:
| 模式 | 说明 | 触发方式 |
|---|---|---|
| 流动模式(flowing) | 数据自动流动 | 监听 data 事件或调用 pipe() |
| 暂停模式(paused) | 需手动读取 | 调用 read() 方法 |
pipe 管道操作:
// 文件复制(内存友好)
const readable = fs.createReadStream('source.txt');
const writable = fs.createWriteStream('dest.txt');
readable.pipe(writable);
// 链式管道
fs.createReadStream('file.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('file.txt.gz'));
背压(Backpressure)处理:
当写入速度跟不上读取速度时,需要处理背压:
const readable = fs.createReadStream('large-file.txt');
const writable = fs.createWriteStream('output.txt');
readable.on('data', (chunk) => {
const canContinue = writable.write(chunk);
if (!canContinue) {
readable.pause(); // 暂停读取
}
});
writable.on('drain', () => {
readable.resume(); // 恢复读取
});
Buffer与二进制处理
Buffer 是 Node.js 用于处理二进制数据的类,在处理文件、网络数据时经常使用。
Buffer 创建方式:
| 方法 | 说明 | 示例 |
|---|---|---|
| Buffer.alloc(size) | 创建指定大小的 Buffer(初始化为0) | Buffer.alloc(10) |
| Buffer.allocUnsafe(size) | 创建未初始化的 Buffer(更快但不安全) | Buffer.allocUnsafe(10) |
| Buffer.from(array) | 从数组创建 | Buffer.from([1, 2, 3]) |
| Buffer.from(string) | 从字符串创建 | Buffer.from('hello') |
| Buffer.from(buffer) | 复制 Buffer | Buffer.from(buf) |
Buffer 常用操作:
| 操作 | 方法 | 说明 |
|---|---|---|
| 写入 | buf.write(string) | 写入字符串 |
| 读取 | buf.toString(encoding) | 转为字符串 |
| 切片 | buf.slice(start, end) | 获取子 Buffer |
| 拼接 | Buffer.concat([buf1, buf2]) | 合并多个 Buffer |
| 比较 | buf.compare(otherBuf) | 比较两个 Buffer |
| 查找 | buf.indexOf(value) | 查找值的位置 |
| 填充 | buf.fill(value) | 填充 Buffer |
编码支持:
| 编码 | 说明 |
|---|---|
| utf8 | 默认编码,多字节 Unicode |
| ascii | 7 位 ASCII |
| base64 | Base64 编码 |
| hex | 十六进制 |
| binary | 二进制(已废弃,使用 latin1) |
| latin1 | ISO-8859-1 |
Buffer 与 TypedArray:
Node.js 的 Buffer 是 Uint8Array 的子类,可以与 TypedArray 互操作:
const buf = Buffer.from([1, 2, 3, 4]);
const arr = new Uint8Array(buf); // 共享内存
// 从 ArrayBuffer 创建
const arrayBuffer = new ArrayBuffer(16);
const buffer = Buffer.from(arrayBuffer);
异步编程模型
异步编程是 Node.js 的核心特性,掌握各种异步模式对于编写高质量的 Node.js 代码至关重要。
回调函数模式
回调函数是 Node.js 最早的异步处理方式,遵循错误优先回调(Error-First Callback) 约定。
错误优先回调约定:
// 回调函数的第一个参数是错误对象
function callback(err, result) {
if (err) {
// 处理错误
return;
}
// 处理结果
}
回调地狱问题:
回调地狱的问题:
| 问题 | 说明 |
|---|---|
| 代码嵌套深 | 多层回调导致代码难以阅读 |
| 错误处理分散 | 每层都需要处理错误 |
| 流程控制困难 | 难以实现并行、串行控制 |
| 调试困难 | 堆栈信息不完整 |
解决回调地狱的方法:
- 命名函数:将回调提取为命名函数
- 模块化:将逻辑拆分到不同模块
- 使用 Promise:现代推荐方式
- 使用 async/await:最佳实践
Promise与async/await
Promise 基础:
Promise 是异步操作的最终完成或失败的表示,有三种状态:
| 状态 | 说明 | 转换 |
|---|---|---|
| pending | 初始状态 | 可转为 fulfilled 或 rejected |
| fulfilled | 操作成功 | 不可再变 |
| rejected | 操作失败 | 不可再变 |
等待中"] -->|resolve| B["fulfilled
已完成"] A -->|reject| C["rejected
已拒绝"] style A fill:#ffeb3b,stroke:#333 style B fill:#4caf50,stroke:#333,color:#fff style C fill:#f44336,stroke:#333,color:#fff
Promise 链式调用:
fetchUser(userId)
.then(user => fetchOrders(user.id))
.then(orders => processOrders(orders))
.then(result => console.log(result))
.catch(err => console.error(err))
.finally(() => cleanup());
Promise 静态方法:
| 方法 | 说明 | 使用场景 |
|---|---|---|
| Promise.all() | 所有 Promise 都成功才成功 | 并行执行,全部需要 |
| Promise.allSettled() | 等待所有 Promise 完成 | 需要所有结果,不管成败 |
| Promise.race() | 返回最先完成的结果 | 超时控制、竞速 |
| Promise.any() | 返回第一个成功的结果 | 多源获取,取最快成功的 |
| Promise.resolve() | 创建已完成的 Promise | 包装同步值 |
| Promise.reject() | 创建已拒绝的 Promise | 创建错误 Promise |
async/await 语法:
async/await 是 Promise 的语法糖,让异步代码看起来像同步代码:
// Promise 方式
function getData() {
return fetch('/api/data')
.then(res => res.json())
.then(data => processData(data));
}
// async/await 方式(推荐)
async function getData() {
const res = await fetch('/api/data');
const data = await res.json();
return processData(data);
}
async/await 错误处理:
// 方式1:try-catch
async function getData() {
try {
const data = await fetchData();
return data;
} catch (err) {
console.error('获取数据失败:', err);
throw err;
}
}
// 方式2:.catch()
async function getData() {
const data = await fetchData().catch(err => {
console.error(err);
return null;
});
return data;
}
并行与串行执行:
// 串行执行(一个接一个)
async function serial() {
const a = await taskA(); // 等待完成
const b = await taskB(); // 再执行
return [a, b];
}
// 并行执行(同时进行)
async function parallel() {
const [a, b] = await Promise.all([
taskA(),
taskB()
]);
return [a, b];
}
常见陷阱:
| 陷阱 | 问题 | 解决方案 |
|---|---|---|
| 循环中的 await | 串行执行,性能差 | 使用 Promise.all |
| 忘记 await | Promise 未等待 | 确保添加 await |
| 错误未捕获 | 未处理的 rejection | 添加 try-catch 或 .catch |
| async 函数返回值 | 总是返回 Promise | 注意调用方式 |
事件驱动编程
事件驱动是 Node.js 的核心编程范式,基于发布-订阅模式。
事件驱动架构:
EventEmitter"] --> B["事件队列"] C["事件监听器1"] --> B D["事件监听器2"] --> B E["事件监听器3"] --> B B --> F["事件循环"] F --> G["执行回调"] end style A fill:#4caf50,stroke:#333,color:#fff style B fill:#ff9800,stroke:#333 style F fill:#2196f3,stroke:#333,color:#fff
事件驱动 vs 传统请求-响应:
| 特性 | 事件驱动 | 请求-响应 |
|---|---|---|
| 耦合度 | 松耦合 | 紧耦合 |
| 扩展性 | 易于扩展 | 扩展困难 |
| 异步支持 | 天然异步 | 需要额外处理 |
| 调试难度 | 较高 | 较低 |
事件驱动最佳实践:
- 合理命名事件:使用清晰、一致的命名
- 限制监听器数量:避免内存泄漏
- 错误事件必须处理:否则会抛出异常
- 使用 once 处理一次性事件:避免重复触发
// 设置最大监听器数量
emitter.setMaxListeners(20);
// 检查监听器数量
if (emitter.listenerCount('event') > 10) {
console.warn('监听器过多');
}
错误处理机制
Node.js 中的错误处理需要特别注意,因为未捕获的错误会导致进程崩溃。
错误类型:
| 类型 | 说明 | 处理方式 |
|---|---|---|
| 同步错误 | 同步代码中的错误 | try-catch |
| 异步回调错误 | 回调函数中的错误 | 错误优先回调 |
| Promise 错误 | Promise 中的错误 | .catch() 或 try-catch |
| 事件错误 | EventEmitter 错误 | error 事件监听 |
| 未捕获异常 | 未处理的错误 | uncaughtException |
| 未处理的 rejection | 未处理的 Promise 拒绝 | unhandledRejection |
全局错误处理:
// 捕获未处理的异常
process.on('uncaughtException', (err) => {
console.error('未捕获的异常:', err);
// 记录日志后退出
process.exit(1);
});
// 捕获未处理的 Promise 拒绝
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的 Promise 拒绝:', reason);
});
错误处理最佳实践:
继续运行"] B -->|不可恢复| D["记录日志
优雅退出"] B -->|编程错误| E["修复代码"] end style A fill:#f44336,stroke:#333,color:#fff style C fill:#4caf50,stroke:#333,color:#fff style D fill:#ff9800,stroke:#333 style E fill:#2196f3,stroke:#333,color:#fff
自定义错误类:
class AppError extends Error {
constructor(message, statusCode, isOperational = true) {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational; // 区分操作错误和编程错误
Error.captureStackTrace(this, this.constructor);
}
}
// 使用
throw new AppError('用户不存在', 404);
错误处理原则:
| 原则 | 说明 |
|---|---|
| 快速失败 | 发现错误立即处理,不要忽略 |
| 区分错误类型 | 操作错误 vs 编程错误 |
| 提供有意义的错误信息 | 便于调试和用户理解 |
| 不要吞掉错误 | 至少记录日志 |
| 优雅降级 | 部分功能失败不影响整体 |
包管理与模块系统
npm包管理器
npm(Node Package Manager)是 Node.js 的默认包管理器,也是世界上最大的软件注册表。
npm 核心功能:
| 功能 | 命令 | 说明 |
|---|---|---|
| 安装包 | npm install <pkg> | 安装依赖包 |
| 全局安装 | npm install -g <pkg> | 安装全局命令行工具 |
| 卸载包 | npm uninstall <pkg> | 移除依赖包 |
| 更新包 | npm update <pkg> | 更新依赖包 |
| 查看信息 | npm info <pkg> | 查看包信息 |
| 搜索包 | npm search <keyword> | 搜索包 |
| 发布包 | npm publish | 发布自己的包 |
| 运行脚本 | npm run <script> | 执行 package.json 中的脚本 |
依赖类型:
| 类型 | 安装命令 | 说明 | 使用场景 |
|---|---|---|---|
| dependencies | npm i <pkg> | 生产依赖 | 运行时需要 |
| devDependencies | npm i -D <pkg> | 开发依赖 | 仅开发时需要 |
| peerDependencies | 手动配置 | 同伴依赖 | 插件声明宿主版本 |
| optionalDependencies | npm i -O <pkg> | 可选依赖 | 安装失败不影响 |
版本号规范(SemVer):
主版本号.次版本号.修订号
MAJOR.MINOR.PATCH
| 符号 | 含义 | 示例 |
|---|---|---|
| ^ | 兼容次版本更新 | ^1.2.3 → 1.x.x |
| ~ | 兼容修订版本更新 | ~1.2.3 → 1.2.x |
| > | 大于指定版本 | >1.2.3 |
| >= | 大于等于 | >=1.2.3 |
| < | 小于 | <1.2.3 |
| = | 精确版本 | =1.2.3 或 1.2.3 |
| ***** | 任意版本 | * |
| x | 通配符 | 1.x |
npm 常用命令速查:
# 初始化项目
npm init -y
# 安装所有依赖
npm install
# 清理缓存
npm cache clean --force
# 查看全局安装位置
npm root -g
# 查看过期的包
npm outdated
# 安全审计
npm audit
# 修复安全问题
npm audit fix
# 查看包的依赖树
npm ls
# 链接本地包(开发调试)
npm link
npm vs yarn vs pnpm:
| 特性 | npm | yarn | pnpm |
|---|---|---|---|
| 速度 | 较慢 | 快 | 最快 |
| 磁盘空间 | 占用大 | 占用大 | 节省空间 |
| 锁文件 | package-lock.json | yarn.lock | pnpm-lock.yaml |
| 工作区 | npm workspaces | yarn workspaces | pnpm workspaces |
| 安全性 | 一般 | 较好 | 较好 |
| 幽灵依赖 | 存在 | 存在 | 不存在 |
npx 工具链
npx 核心机制
npx 是 Node.js 自带的包执行工具,其设计目标是 “无需安装,直接运行” npm 注册表中的包。
工作原理:
- 检查本地
node_modules/.bin目录是否存在该命令。 - 若本地不存在,则从 npm 仓库临时下载指定的包到缓存目录。
- 执行该包中的命令。
- 执行完毕后,不进行全局安装,保持环境清洁。
与 npm 的区别:
| 命令 | 角色 | 关键行为 |
|---|---|---|
npm install | 包管理工具 | 将包安装到本地 node_modules 或全局。 |
npx <command> | 包执行工具 | 临时运行包中的命令,优先使用本地,其次远程拉取。 |
典型使用场景:
- 运行一次性脚手架工具:
npx create-react-app my-app - 运行项目本地依赖的命令:
npx eslint src/ - 指定版本运行:
npx [email protected] tsc --init - 执行远程脚本:
npx degit user/repo my-project
局限性:npx 不适合运行需要长时间驻留、消耗大量资源的服务(如本地大模型),因为这违背其“临时执行”的设计初衷,容易因资源问题失败。
npx wrangler 详解
npx wrangler 是使用 npx 运行 Cloudflare 的官方命令行工具 wrangler,用于开发和部署 Cloudflare 生态的各项服务。
wrangler 核心功能:
| 功能 | 说明 |
|---|---|
| Workers | 开发、调试、部署边缘 JavaScript/WebAssembly 函数。 |
| Pages | 管理 Pages 项目及 Pages Functions。 |
| KV | 操作 Key-Value 存储命名空间。 |
| R2 | 管理对象存储桶和文件。 |
| D1 | 操作 SQLite 数据库。 |
| Queues | 管理消息队列。 |
常用命令:
# 1. 初始化一个 Worker 项目
npx wrangler init my-worker
# 2. 本地开发调试(启动本地模拟环境)
npx wrangler dev
# 3. 部署到 Cloudflare 全球网络
npx wrangler deploy
# 4. 登录 Cloudflare 账号
npx wrangler login
环境适配:在云 IDE 中使用 npx wrangler dev 是合理的,因为 Workers 是轻量的边缘计算模型,符合云 IDE 的资源限制。但若试图在 Worker 中运行重计算模型,则会遭遇与之前所述相同的资源瓶颈。
CommonJS模块规范
CommonJS 是 Node.js 的原生模块系统,使用 require 和 module.exports。
模块导出方式:
// 方式1:module.exports 导出对象
module.exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
// 方式2:module.exports 导出单个值
module.exports = function add(a, b) {
return a + b;
};
// 方式3:exports 快捷方式
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;
模块导入方式:
// 导入整个模块
const math = require('./math');
math.add(1, 2);
// 解构导入
const { add, subtract } = require('./math');
add(1, 2);
// 导入核心模块
const fs = require('fs');
// 导入 node_modules 中的包
const express = require('express');
模块解析规则:
模块缓存机制:
// 模块只会加载一次,后续 require 返回缓存
const a = require('./module');
const b = require('./module');
console.log(a === b); // true
// 查看缓存
console.log(require.cache);
// 清除缓存(谨慎使用)
delete require.cache[require.resolve('./module')];
CommonJS 特点:
| 特点 | 说明 |
|---|---|
| 同步加载 | require 是同步的 |
| 运行时加载 | 模块在运行时解析 |
| 值拷贝 | 导出的是值的拷贝 |
| 单例模式 | 模块只执行一次 |
| 动态导入 | 可以在任意位置 require |
ES Modules
ES Modules(ESM)是 JavaScript 的官方标准模块系统,Node.js 12+ 开始稳定支持。
启用 ESM 的方式:
| 方式 | 说明 |
|---|---|
| 文件扩展名 .mjs | 单个文件使用 ESM |
| package.json 设置 type: “module” | 整个项目使用 ESM |
ESM 导出语法:
// 命名导出
export const name = 'Node.js';
export function add(a, b) { return a + b; }
// 默认导出
export default class Calculator { }
// 混合导出
export { name, add };
export default Calculator;
// 重命名导出
export { add as sum };
ESM 导入语法:
// 命名导入
import { name, add } from './module.js';
// 默认导入
import Calculator from './module.js';
// 混合导入
import Calculator, { name, add } from './module.js';
// 重命名导入
import { add as sum } from './module.js';
// 导入所有
import * as math from './module.js';
// 动态导入
const module = await import('./module.js');
CommonJS vs ES Modules:
| 特性 | CommonJS | ES Modules |
|---|---|---|
| 语法 | require/module.exports | import/export |
| 加载方式 | 同步 | 异步 |
| 加载时机 | 运行时 | 编译时(静态分析) |
| 导出值 | 值拷贝 | 值引用(实时绑定) |
| 顶层 this | module.exports | undefined |
| 动态导入 | require() | import() |
| Tree Shaking | 不支持 | 支持 |
| 循环依赖 | 部分支持 | 完全支持 |
ESM 中使用 CommonJS:
// ESM 中导入 CommonJS 模块
import pkg from 'commonjs-package';
// 使用 createRequire
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const pkg = require('commonjs-package');
ESM 特有功能:
// import.meta 元信息
console.log(import.meta.url); // 当前模块的 URL
// 顶层 await(Node.js 14.8+)
const data = await fetch('/api/data');
package.json配置详解
package.json 是 Node.js 项目的核心配置文件,定义了项目的元数据和依赖。
核心字段:
| 字段 | 说明 | 示例 |
|---|---|---|
| name | 包名称 | "my-package" |
| version | 版本号 | "1.0.0" |
| description | 描述 | "A sample package" |
| main | CommonJS 入口 | "index.js" |
| module | ESM 入口 | "index.mjs" |
| exports | 导出映射 | 见下文 |
| type | 模块类型 | "module" 或 "commonjs" |
| scripts | 脚本命令 | {"start": "node app.js"} |
| dependencies | 生产依赖 | {"express": "^4.18.0"} |
| devDependencies | 开发依赖 | {"jest": "^29.0.0"} |
| engines | Node 版本要求 | {"node": ">=16.0.0"} |
| files | 发布包含的文件 | ["dist", "lib"] |
| bin | 可执行文件 | {"cli": "./bin/cli.js"} |
exports 字段(条件导出):
{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
}
}
scripts 常用配置:
{
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
"build": "tsc",
"test": "jest",
"lint": "eslint src/",
"preinstall": "echo 安装前执行",
"postinstall": "echo 安装后执行",
"prepublishOnly": "npm run build"
}
}
生命周期脚本:
workspaces(Monorepo 支持):
{
"workspaces": [
"packages/*"
]
}
Web开发框架
Express框架
Express 是 Node.js 最流行的 Web 框架,以简洁、灵活著称,是学习 Node.js Web 开发的首选。
Express 核心概念:
中间件机制:
中间件是 Express 的核心,每个中间件可以:
- 执行任何代码
- 修改请求和响应对象
- 结束请求-响应循环
- 调用下一个中间件
| 中间件类型 | 说明 | 示例 |
|---|---|---|
| 应用级中间件 | 绑定到 app 实例 | app.use(middleware) |
| 路由级中间件 | 绑定到 router 实例 | router.use(middleware) |
| 错误处理中间件 | 处理错误 | app.use((err, req, res, next) => {}) |
| 内置中间件 | Express 内置 | express.json(), express.static() |
| 第三方中间件 | npm 安装 | cors, helmet, morgan |
常用内置中间件:
| 中间件 | 功能 |
|---|---|
| express.json() | 解析 JSON 请求体 |
| express.urlencoded() | 解析 URL 编码请求体 |
| express.static() | 提供静态文件服务 |
| express.Router() | 创建模块化路由 |
路由定义:
// 基本路由
app.get('/users', (req, res) => { });
app.post('/users', (req, res) => { });
app.put('/users/:id', (req, res) => { });
app.delete('/users/:id', (req, res) => { });
// 路由参数
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
});
// 查询参数
app.get('/search', (req, res) => {
const keyword = req.query.q;
});
// 路由分组
const userRouter = express.Router();
userRouter.get('/', listUsers);
userRouter.post('/', createUser);
app.use('/api/users', userRouter);
请求对象(req)常用属性:
| 属性 | 说明 |
|---|---|
| req.params | 路由参数 |
| req.query | 查询字符串参数 |
| req.body | 请求体(需要中间件解析) |
| req.headers | 请求头 |
| req.cookies | Cookie(需要 cookie-parser) |
| req.ip | 客户端 IP |
| req.path | 请求路径 |
| req.method | 请求方法 |
响应对象(res)常用方法:
| 方法 | 说明 |
|---|---|
| res.send() | 发送响应 |
| res.json() | 发送 JSON 响应 |
| res.status() | 设置状态码 |
| res.redirect() | 重定向 |
| res.render() | 渲染模板 |
| res.sendFile() | 发送文件 |
| res.download() | 下载文件 |
| res.cookie() | 设置 Cookie |
Koa框架
Koa 是 Express 原班人马打造的下一代 Web 框架,更轻量、更现代,基于 async/await。
Koa vs Express:
| 特性 | Express | Koa |
|---|---|---|
| 异步处理 | 回调/Promise | async/await 原生支持 |
| 中间件模型 | 线性 | 洋葱模型 |
| 内置功能 | 较多 | 极简,需要插件 |
| 错误处理 | 需要专门中间件 | try-catch 即可 |
| 体积 | 较大 | 更小 |
| 学习曲线 | 较低 | 稍高 |
洋葱模型:
Koa 中间件示例:
app.use(async (ctx, next) => {
console.log('1. 进入中间件1');
await next();
console.log('6. 离开中间件1');
});
app.use(async (ctx, next) => {
console.log('2. 进入中间件2');
await next();
console.log('5. 离开中间件2');
});
app.use(async (ctx) => {
console.log('3. 处理请求');
ctx.body = 'Hello';
console.log('4. 处理完成');
});
// 输出顺序:1 -> 2 -> 3 -> 4 -> 5 -> 6
Koa Context 对象:
| 属性/方法 | 说明 |
|---|---|
| ctx.request | Koa Request 对象 |
| ctx.response | Koa Response 对象 |
| ctx.req | Node.js 原生 request |
| ctx.res | Node.js 原生 response |
| ctx.body | 响应体 |
| ctx.status | 响应状态码 |
| ctx.params | 路由参数(需要路由中间件) |
| ctx.query | 查询参数 |
| ctx.throw() | 抛出 HTTP 错误 |
NestJS框架
NestJS 是一个企业级 Node.js 框架,深受 Angular 启发,使用 TypeScript 构建,提供完整的架构方案。
NestJS 核心特性:
| 特性 | 说明 |
|---|---|
| TypeScript 优先 | 完整的类型支持 |
| 依赖注入 | IoC 容器,松耦合 |
| 模块化架构 | 清晰的代码组织 |
| 装饰器 | 声明式编程 |
| 内置支持 | WebSocket、GraphQL、微服务 |
| 测试友好 | 内置测试工具 |
NestJS 架构:
模块"] --> B["Controller
控制器"] A --> C["Provider
提供者"] B --> D["Service
服务"] C --> D D --> E["Repository
仓库"] end F["HTTP 请求"] --> B B --> G["HTTP 响应"] style A fill:#e3f2fd,stroke:#333 style B fill:#c8e6c9,stroke:#333 style D fill:#fff9c4,stroke:#333
核心概念:
| 概念 | 说明 | 装饰器 |
|---|---|---|
| Module | 组织代码的容器 | @Module() |
| Controller | 处理 HTTP 请求 | @Controller() |
| Provider | 可注入的服务 | @Injectable() |
| Middleware | 请求预处理 | 实现 NestMiddleware |
| Guard | 权限验证 | @UseGuards() |
| Interceptor | 请求/响应拦截 | @UseInterceptors() |
| Pipe | 数据转换和验证 | @UsePipes() |
| Filter | 异常处理 | @Catch() |
请求生命周期:
前置"] D --> E["Pipe"] E --> F["Controller"] F --> G["Interceptor
后置"] G --> H["Filter
异常处理"] H --> I["响应"] style A fill:#e3f2fd,stroke:#333 style F fill:#c8e6c9,stroke:#333 style I fill:#ffccbc,stroke:#333
Fastify框架
Fastify 是一个高性能 Web 框架,专注于提供最佳的开发体验和最快的执行速度。
Fastify 特点:
| 特点 | 说明 |
|---|---|
| 高性能 | 比 Express 快约 2 倍 |
| Schema 验证 | 内置 JSON Schema 验证 |
| 插件系统 | 强大的插件架构 |
| TypeScript 支持 | 良好的类型支持 |
| 日志集成 | 内置 Pino 日志 |
| 钩子系统 | 丰富的生命周期钩子 |
Fastify 生命周期钩子:
框架对比与选型
性能对比(请求/秒):
| 框架 | 性能 | 说明 |
|---|---|---|
| Fastify | ~75,000 | 最快 |
| Koa | ~50,000 | 较快 |
| Express | ~35,000 | 中等 |
| NestJS | ~30,000 | 功能丰富 |
选型建议:
框架选型总结:
| 场景 | 推荐框架 | 原因 |
|---|---|---|
| 快速原型 | Express | 简单、资料多 |
| 高性能 API | Fastify | 性能最优 |
| 企业级应用 | NestJS | 架构完整、TypeScript |
| 轻量级服务 | Koa | 现代、灵活 |
| 微服务 | NestJS/Fastify | 内置支持 |
| 学习入门 | Express | 生态最成熟 |
数据库操作
MySQL操作
Node.js 连接 MySQL 主要使用 mysql2 库,支持 Promise 和连接池。
连接方式对比:
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 单连接 | 每次创建新连接 | 简单脚本 |
| 连接池 | 复用连接 | 生产环境 |
| Promise 包装 | 支持 async/await | 现代开发 |
连接池配置:
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'mydb',
waitForConnections: true,
connectionLimit: 10, // 最大连接数
queueLimit: 0, // 等待队列限制
enableKeepAlive: true, // 保持连接
keepAliveInitialDelay: 0
});
CRUD 操作:
// 查询
const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [userId]);
// 插入
const [result] = await pool.query(
'INSERT INTO users (name, email) VALUES (?, ?)',
[name, email]
);
console.log('插入ID:', result.insertId);
// 更新
const [result] = await pool.query(
'UPDATE users SET name = ? WHERE id = ?',
[newName, userId]
);
console.log('影响行数:', result.affectedRows);
// 删除
const [result] = await pool.query('DELETE FROM users WHERE id = ?', [userId]);
事务处理:
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
await connection.query('UPDATE accounts SET balance = balance - ? WHERE id = ?', [amount, fromId]);
await connection.query('UPDATE accounts SET balance = balance + ? WHERE id = ?', [amount, toId]);
await connection.commit();
} catch (err) {
await connection.rollback();
throw err;
} finally {
connection.release();
}
防止 SQL 注入:
| 方法 | 说明 |
|---|---|
| 参数化查询 | 使用 ? 占位符 |
| escape | mysql.escape(value) |
| escapeId | mysql.escapeId(identifier) |
MongoDB操作
MongoDB 是最流行的 NoSQL 数据库,Node.js 使用 mongodb 官方驱动或 mongoose ODM。
MongoDB 驱动 vs Mongoose:
| 特性 | mongodb 驱动 | mongoose |
|---|---|---|
| 类型 | 原生驱动 | ODM |
| Schema | 无 | 有 |
| 验证 | 手动 | 内置 |
| 中间件 | 无 | 有 |
| 性能 | 更快 | 略慢 |
| 学习曲线 | 低 | 中 |
Mongoose 基本使用:
const mongoose = require('mongoose');
// 连接数据库
await mongoose.connect('mongodb://localhost:27017/mydb');
// 定义 Schema
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, unique: true },
age: { type: Number, min: 0 },
createdAt: { type: Date, default: Date.now }
});
// 创建 Model
const User = mongoose.model('User', userSchema);
CRUD 操作:
// 创建
const user = await User.create({ name: 'John', email: '[email protected]' });
// 查询
const users = await User.find({ age: { $gte: 18 } });
const user = await User.findById(id);
const user = await User.findOne({ email: '[email protected]' });
// 更新
await User.updateOne({ _id: id }, { name: 'Jane' });
await User.findByIdAndUpdate(id, { name: 'Jane' }, { new: true });
// 删除
await User.deleteOne({ _id: id });
await User.findByIdAndDelete(id);
Mongoose 中间件:
// 保存前中间件
userSchema.pre('save', async function(next) {
if (this.isModified('password')) {
this.password = await bcrypt.hash(this.password, 10);
}
next();
});
// 查询后中间件
userSchema.post('find', function(docs) {
console.log('查询到', docs.length, '条记录');
});
Redis操作
Redis 是高性能的内存数据库,常用于缓存、会话存储、消息队列。
Node.js Redis 客户端:
| 库 | 说明 |
|---|---|
| ioredis | 功能丰富,支持集群 |
| redis | 官方库,v4 后支持 Promise |
ioredis 基本操作:
const Redis = require('ioredis');
const redis = new Redis({
host: 'localhost',
port: 6379,
password: 'password',
db: 0
});
// 字符串操作
await redis.set('key', 'value');
await redis.set('key', 'value', 'EX', 3600); // 设置过期时间
const value = await redis.get('key');
// 哈希操作
await redis.hset('user:1', 'name', 'John', 'age', '25');
const user = await redis.hgetall('user:1');
// 列表操作
await redis.lpush('queue', 'task1', 'task2');
const task = await redis.rpop('queue');
// 集合操作
await redis.sadd('tags', 'node', 'javascript');
const tags = await redis.smembers('tags');
// 有序集合
await redis.zadd('leaderboard', 100, 'player1', 200, 'player2');
const top = await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES');
Redis 常用场景:
| 场景 | 数据结构 | 说明 |
|---|---|---|
| 缓存 | String | 缓存数据库查询结果 |
| 会话存储 | String/Hash | 存储用户会话 |
| 计数器 | String | 访问量、点赞数 |
| 排行榜 | Sorted Set | 游戏排名 |
| 消息队列 | List | 任务队列 |
| 发布订阅 | Pub/Sub | 实时通知 |
| 分布式锁 | String + NX | 防止并发问题 |
分布式锁实现:
// 获取锁
async function acquireLock(key, ttl = 10000) {
const lockKey = `lock:${key}`;
const lockValue = Date.now().toString();
const result = await redis.set(lockKey, lockValue, 'PX', ttl, 'NX');
return result === 'OK' ? lockValue : null;
}
// 释放锁(使用 Lua 脚本保证原子性)
async function releaseLock(key, lockValue) {
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
return await redis.eval(script, 1, `lock:${key}`, lockValue);
}
ORM框架使用
ORM(Object-Relational Mapping)将数据库表映射为对象,简化数据库操作。
主流 ORM 对比:
| ORM | 数据库支持 | TypeScript | 特点 |
|---|---|---|---|
| Sequelize | MySQL, PostgreSQL, SQLite, MSSQL | 支持 | 功能全面,社区大 |
| TypeORM | 多种 | 原生 | TypeScript 优先 |
| Prisma | 多种 | 原生 | 现代、类型安全 |
| Knex.js | 多种 | 支持 | 查询构建器 |
Prisma 使用示例:
// schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
author User @relation(fields: [authorId], references: [id])
authorId Int
}
// 使用 Prisma Client
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
// 创建
const user = await prisma.user.create({
data: { email: '[email protected]', name: 'John' }
});
// 查询(包含关联)
const userWithPosts = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: true }
});
// 事务
await prisma.$transaction([
prisma.user.update({ where: { id: 1 }, data: { name: 'Jane' } }),
prisma.post.create({ data: { title: 'Hello', authorId: 1 } })
]);
ORM 选型建议:
| 场景 | 推荐 | 原因 |
|---|---|---|
| TypeScript 项目 | Prisma/TypeORM | 类型安全 |
| 快速开发 | Prisma | 开发体验好 |
| 复杂查询 | Knex.js | 灵活的查询构建 |
| 传统项目 | Sequelize | 成熟稳定 |
性能优化
内存管理与优化
Node.js 使用 V8 引擎的自动垃圾回收,但不当的代码仍可能导致内存泄漏。
V8 内存限制:
| 系统 | 默认限制 | 说明 |
|---|---|---|
| 64位系统 | ~1.4GB | 老生代约 1.4GB |
| 32位系统 | ~0.7GB | 老生代约 0.7GB |
调整内存限制:
# 设置最大老生代内存为 4GB
node --max-old-space-size=4096 app.js
# 设置最大新生代内存
node --max-semi-space-size=64 app.js
常见内存泄漏场景:
| 场景 | 原因 | 解决方案 |
|---|---|---|
| 全局变量 | 意外创建全局变量 | 使用 strict 模式 |
| 闭包 | 闭包持有大对象引用 | 及时释放引用 |
| 事件监听器 | 未移除的监听器 | 使用 once 或手动移除 |
| 定时器 | 未清除的定时器 | clearInterval/clearTimeout |
| 缓存无限增长 | 缓存没有过期策略 | 使用 LRU 缓存 |
| 大数组/对象 | 持续增长的数据结构 | 定期清理 |
内存泄漏检测:
内存监控代码:
// 获取内存使用情况
const used = process.memoryUsage();
console.log({
rss: `${Math.round(used.rss / 1024 / 1024)} MB`, // 常驻内存
heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)} MB`, // 堆总量
heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)} MB`, // 堆使用量
external: `${Math.round(used.external / 1024 / 1024)} MB` // C++ 对象
});
// 定期监控
setInterval(() => {
const { heapUsed } = process.memoryUsage();
if (heapUsed > 500 * 1024 * 1024) { // 超过 500MB
console.warn('内存使用过高');
}
}, 30000);
内存优化技巧:
| 技巧 | 说明 |
|---|---|
| 使用 Stream | 处理大文件避免一次性加载 |
| 及时释放引用 | 不再使用的对象设为 null |
| 使用 Buffer 池 | Buffer.allocUnsafe + 手动管理 |
| 限制缓存大小 | 使用 LRU 缓存策略 |
| 避免大对象 | 拆分大对象,分批处理 |
| 使用 WeakMap/WeakSet | 允许垃圾回收 |
CPU密集型任务处理
Node.js 单线程模型不适合 CPU 密集型任务,需要特殊处理。
CPU 密集型任务的问题:
阻塞10秒"] B --> C["响应1"] D["请求2"] -.->|等待| B E["请求3"] -.->|等待| B end style B fill:#f44336,stroke:#333,color:#fff
解决方案对比:
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Worker Threads | 计算密集型 | 共享内存,通信快 | 复杂度高 |
| Child Process | 独立任务 | 隔离性好 | 通信开销大 |
| Cluster | Web 服务扩展 | 简单 | 不适合单任务 |
| 外部服务 | 专业计算 | 专业优化 | 网络开销 |
Worker Threads 示例:
// main.js
const { Worker } = require('worker_threads');
function runWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.js', {
workerData: data
});
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) reject(new Error(`Worker stopped with code ${code}`));
});
});
}
// worker.js
const { parentPort, workerData } = require('worker_threads');
// 执行 CPU 密集型计算
function heavyComputation(data) {
// 复杂计算...
return result;
}
const result = heavyComputation(workerData);
parentPort.postMessage(result);
Worker 线程池:
const { Worker } = require('worker_threads');
const os = require('os');
class WorkerPool {
constructor(workerPath, poolSize = os.cpus().length) {
this.workerPath = workerPath;
this.poolSize = poolSize;
this.workers = [];
this.queue = [];
this.init();
}
init() {
for (let i = 0; i < this.poolSize; i++) {
this.addWorker();
}
}
addWorker() {
const worker = new Worker(this.workerPath);
worker.on('message', (result) => {
worker.currentResolve(result);
worker.busy = false;
this.processQueue();
});
worker.busy = false;
this.workers.push(worker);
}
runTask(data) {
return new Promise((resolve, reject) => {
const task = { data, resolve, reject };
this.queue.push(task);
this.processQueue();
});
}
processQueue() {
if (this.queue.length === 0) return;
const worker = this.workers.find(w => !w.busy);
if (!worker) return;
const task = this.queue.shift();
worker.busy = true;
worker.currentResolve = task.resolve;
worker.postMessage(task.data);
}
}
集群与负载均衡
使用 cluster 模块可以充分利用多核 CPU。
Cluster 工作原理:
Cluster 基本使用:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork(); // 重启 worker
});
} else {
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World\n');
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
负载均衡策略:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 轮询(Round-Robin) | 默认策略,依次分发 | 通用场景 |
| 最少连接 | 分发给连接数最少的 | 长连接场景 |
| IP Hash | 同一 IP 分发到同一 Worker | 会话保持 |
性能监控与分析
性能指标:
| 指标 | 说明 | 关注点 |
|---|---|---|
| 响应时间 | 请求处理耗时 | P50, P95, P99 |
| 吞吐量 | 每秒处理请求数 | QPS/TPS |
| 错误率 | 错误请求占比 | 5xx 错误 |
| CPU 使用率 | CPU 占用情况 | 是否过高 |
| 内存使用 | 内存占用情况 | 是否泄漏 |
| 事件循环延迟 | 事件循环阻塞时间 | 是否阻塞 |
事件循环监控:
// 监控事件循环延迟
const start = process.hrtime();
setImmediate(() => {
const delta = process.hrtime(start);
const nanosec = delta[0] * 1e9 + delta[1];
const ms = nanosec / 1e6;
if (ms > 100) {
console.warn(`事件循环延迟: ${ms}ms`);
}
});
// 使用 perf_hooks
const { monitorEventLoopDelay } = require('perf_hooks');
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
setInterval(() => {
console.log({
min: h.min / 1e6,
max: h.max / 1e6,
mean: h.mean / 1e6,
p99: h.percentile(99) / 1e6
});
}, 5000);
性能分析工具:
| 工具 | 用途 | 说明 |
|---|---|---|
| –inspect | Chrome DevTools 调试 | 内置 |
| clinic.js | 性能诊断套件 | 推荐 |
| 0x | 火焰图生成 | CPU 分析 |
| heapdump | 堆快照 | 内存分析 |
| v8-profiler | V8 性能分析 | 详细分析 |
使用 Chrome DevTools:
# 启动调试模式
node --inspect app.js
# 打开 Chrome 访问
chrome://inspect
性能优化检查清单:
| 检查项 | 说明 |
|---|---|
| ✅ 使用连接池 | 数据库、Redis 连接复用 |
| ✅ 启用 Gzip | 压缩响应数据 |
| ✅ 使用缓存 | Redis 缓存热点数据 |
| ✅ 异步操作 | 避免同步阻塞 |
| ✅ Stream 处理 | 大文件流式处理 |
| ✅ 集群部署 | 利用多核 CPU |
| ✅ 负载均衡 | Nginx 反向代理 |
| ✅ 监控告警 | 及时发现问题 |
安全实践
常见安全漏洞
OWASP Top 10 在 Node.js 中的体现:
| 漏洞类型 | 说明 | Node.js 风险点 |
|---|---|---|
| 注入攻击 | SQL/NoSQL/命令注入 | 数据库查询、exec |
| 身份认证失效 | 弱密码、会话管理 | JWT、Session |
| 敏感数据泄露 | 数据未加密 | 日志、响应 |
| XXE | XML 外部实体 | XML 解析 |
| 访问控制失效 | 越权访问 | API 权限 |
| 安全配置错误 | 默认配置、调试模式 | 生产环境配置 |
| XSS | 跨站脚本 | 模板渲染 |
| 不安全的反序列化 | 恶意对象 | JSON.parse |
| 使用含漏洞的组件 | 依赖漏洞 | npm 包 |
| 日志和监控不足 | 无法追踪攻击 | 日志记录 |
SQL 注入防护:
// ❌ 危险:字符串拼接
const query = `SELECT * FROM users WHERE id = ${userId}`;
// ✅ 安全:参数化查询
const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [userId]);
// ✅ 安全:使用 ORM
const user = await User.findByPk(userId);
NoSQL 注入防护:
// ❌ 危险:直接使用用户输入
const user = await User.findOne({ username: req.body.username });
// 攻击者可以传入 { "$gt": "" } 绕过验证
// ✅ 安全:类型验证
const username = String(req.body.username);
const user = await User.findOne({ username });
// ✅ 安全:使用 Schema 验证
const schema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required()
});
命令注入防护:
// ❌ 危险:直接拼接命令
const { exec } = require('child_process');
exec(`ls ${userInput}`); // 用户可输入 "; rm -rf /"
// ✅ 安全:使用 execFile 或参数数组
const { execFile } = require('child_process');
execFile('ls', [userInput]);
// ✅ 安全:使用 spawn 并禁用 shell
const { spawn } = require('child_process');
spawn('ls', [userInput], { shell: false });
XSS 防护:
// ❌ 危险:直接输出用户内容
res.send(`<div>${userInput}</div>`);
// ✅ 安全:转义 HTML
const escapeHtml = require('escape-html');
res.send(`<div>${escapeHtml(userInput)}</div>`);
// ✅ 安全:使用模板引擎自动转义
// EJS 默认转义
<%= userInput %>
// ✅ 安全:设置 CSP 头
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"]
}
}));
安全编码规范
安全中间件 - Helmet:
const helmet = require('helmet');
app.use(helmet());
// Helmet 包含的安全头
// X-DNS-Prefetch-Control
// X-Frame-Options
// X-Content-Type-Options
// X-XSS-Protection
// Strict-Transport-Security
// Content-Security-Policy
输入验证:
const Joi = require('joi');
const userSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
email: Joi.string().email().required(),
password: Joi.string().pattern(/^[a-zA-Z0-9]{8,30}$/).required(),
age: Joi.number().integer().min(0).max(150)
});
// 验证
const { error, value } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
速率限制:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100, // 最多 100 次请求
message: '请求过于频繁,请稍后再试',
standardHeaders: true,
legacyHeaders: false
});
app.use('/api/', limiter);
// 登录接口更严格的限制
const loginLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 小时
max: 5, // 最多 5 次
message: '登录尝试次数过多'
});
app.use('/api/login', loginLimiter);
安全配置检查清单:
| 检查项 | 说明 |
|---|---|
| ✅ 使用 HTTPS | 加密传输 |
| ✅ 设置安全头 | 使用 Helmet |
| ✅ 输入验证 | 验证所有用户输入 |
| ✅ 参数化查询 | 防止注入 |
| ✅ 速率限制 | 防止暴力攻击 |
| ✅ 错误处理 | 不泄露敏感信息 |
| ✅ 依赖审计 | npm audit |
| ✅ 环境变量 | 敏感配置不硬编码 |
| ✅ 日志脱敏 | 不记录敏感数据 |
| ✅ CORS 配置 | 限制跨域访问 |
认证与授权
认证方式对比:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Session | 服务端控制,可即时失效 | 需要存储,扩展性差 | 传统 Web |
| JWT | 无状态,易扩展 | 无法即时失效 | API、微服务 |
| OAuth 2.0 | 第三方授权 | 复杂 | 第三方登录 |
JWT 实现:
const jwt = require('jsonwebtoken');
// 生成 Token
function generateToken(user) {
return jwt.sign(
{ id: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
}
// 验证中间件
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: '未提供 Token' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: 'Token 无效' });
}
}
JWT 安全最佳实践:
| 实践 | 说明 |
|---|---|
| 使用强密钥 | 至少 256 位随机字符串 |
| 设置过期时间 | 不要设置过长 |
| 使用 HTTPS | 防止 Token 被截获 |
| 不存储敏感信息 | Payload 可被解码 |
| 实现 Token 刷新 | 短期 Token + 刷新机制 |
| 黑名单机制 | 支持 Token 失效 |
RBAC 权限控制:
// 角色定义
const roles = {
admin: ['read', 'write', 'delete', 'manage'],
editor: ['read', 'write'],
viewer: ['read']
};
// 权限检查中间件
function checkPermission(permission) {
return (req, res, next) => {
const userRole = req.user.role;
const permissions = roles[userRole] || [];
if (!permissions.includes(permission)) {
return res.status(403).json({ error: '权限不足' });
}
next();
};
}
// 使用
app.delete('/api/posts/:id',
authMiddleware,
checkPermission('delete'),
deletePost
);
密码安全:
const bcrypt = require('bcrypt');
// 密码加密
async function hashPassword(password) {
const saltRounds = 12; // 推荐 10-12
return await bcrypt.hash(password, saltRounds);
}
// 密码验证
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
// 密码强度验证
function validatePassword(password) {
const minLength = 8;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*]/.test(password);
return password.length >= minLength &&
hasUpperCase && hasLowerCase &&
hasNumbers && hasSpecialChar;
}
部署与运维
PM2进程管理
PM2 是 Node.js 应用的生产级进程管理器,提供进程守护、负载均衡、日志管理等功能。
PM2 核心功能:
| 功能 | 说明 |
|---|---|
| 进程守护 | 应用崩溃自动重启 |
| 负载均衡 | 集群模式,利用多核 |
| 零停机重启 | 平滑重启,不中断服务 |
| 日志管理 | 集中管理应用日志 |
| 监控 | 内存、CPU 监控 |
| 启动脚本 | 开机自启动 |
常用命令:
# 启动应用
pm2 start app.js
pm2 start app.js --name "my-app"
pm2 start app.js -i max # 集群模式,使用所有 CPU
# 管理应用
pm2 list # 查看所有应用
pm2 stop <app> # 停止应用
pm2 restart <app> # 重启应用
pm2 reload <app> # 零停机重启
pm2 delete <app> # 删除应用
# 日志
pm2 logs # 查看所有日志
pm2 logs <app> # 查看指定应用日志
pm2 flush # 清空日志
# 监控
pm2 monit # 实时监控面板
pm2 show <app> # 查看应用详情
# 启动脚本
pm2 startup # 生成启动脚本
pm2 save # 保存当前进程列表
ecosystem.config.js 配置:
module.exports = {
apps: [{
name: 'my-app',
script: './app.js',
instances: 'max', // 集群实例数
exec_mode: 'cluster', // 集群模式
watch: false, // 生产环境关闭
max_memory_restart: '1G', // 内存超限重启
env: {
NODE_ENV: 'development'
},
env_production: {
NODE_ENV: 'production',
PORT: 3000
},
// 日志配置
log_date_format: 'YYYY-MM-DD HH:mm:ss',
error_file: './logs/error.log',
out_file: './logs/out.log',
merge_logs: true,
// 重启策略
exp_backoff_restart_delay: 100,
max_restarts: 10,
min_uptime: '10s'
}]
};
使用配置文件:
# 启动
pm2 start ecosystem.config.js --env production
# 重启
pm2 reload ecosystem.config.js --env production
Docker容器化部署
Dockerfile 最佳实践:
# 使用官方 Node.js 镜像
FROM node:18-alpine AS builder
# 设置工作目录
WORKDIR /app
# 复制 package 文件
COPY package*.json ./
# 安装依赖(仅生产依赖)
RUN npm ci --only=production
# 复制源代码
COPY . .
# 构建(如果需要)
RUN npm run build
# 生产镜像
FROM node:18-alpine
WORKDIR /app
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# 复制构建产物
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./
# 切换用户
USER nodejs
# 暴露端口
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js || exit 1
# 启动命令
CMD ["node", "dist/app.js"]
docker-compose.yml:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=mongodb://mongo:27017/mydb
- REDIS_URL=redis://redis:6379
depends_on:
- mongo
- redis
restart: unless-stopped
deploy:
replicas: 2
resources:
limits:
memory: 512M
cpus: '0.5'
mongo:
image: mongo:6
volumes:
- mongo_data:/data/db
restart: unless-stopped
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
mongo_data:
Docker 优化技巧:
| 技巧 | 说明 |
|---|---|
| 多阶段构建 | 减小镜像体积 |
| 使用 Alpine | 更小的基础镜像 |
| npm ci | 更快、更可靠的安装 |
| 非 root 用户 | 安全最佳实践 |
| 健康检查 | 容器健康监控 |
| .dockerignore | 排除不需要的文件 |
日志管理
日志级别:
| 级别 | 说明 | 使用场景 |
|---|---|---|
| error | 错误 | 需要立即处理的问题 |
| warn | 警告 | 潜在问题 |
| info | 信息 | 重要业务事件 |
| debug | 调试 | 开发调试信息 |
| trace | 追踪 | 详细追踪信息 |
Winston 日志库:
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'my-app' },
transports: [
// 错误日志
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
// 所有日志
new winston.transports.File({
filename: 'logs/combined.log'
})
]
});
// 开发环境输出到控制台
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}
// 使用
logger.info('用户登录', { userId: 123, ip: '192.168.1.1' });
logger.error('数据库连接失败', { error: err.message });
日志最佳实践:
| 实践 | 说明 |
|---|---|
| 结构化日志 | 使用 JSON 格式 |
| 添加上下文 | 请求 ID、用户 ID |
| 日志轮转 | 按大小或时间切割 |
| 敏感信息脱敏 | 不记录密码、Token |
| 集中收集 | ELK、Loki 等 |
| 合理级别 | 生产环境用 info |
请求日志中间件:
const morgan = require('morgan');
// 自定义格式
morgan.token('request-id', (req) => req.id);
app.use(morgan(':request-id :method :url :status :response-time ms'));
// 生产环境使用 combined 格式
if (process.env.NODE_ENV === 'production') {
app.use(morgan('combined', {
stream: { write: (msg) => logger.info(msg.trim()) }
}));
}
监控与告警
监控指标体系:
CPU、内存、磁盘、网络"] B["应用监控
响应时间、错误率、QPS"] C["业务监控
订单量、转化率"] end A --> D["告警系统"] B --> D C --> D D --> E["通知渠道
邮件、短信、钉钉"] style A fill:#e3f2fd,stroke:#333 style B fill:#c8e6c9,stroke:#333 style C fill:#fff9c4,stroke:#333 style D fill:#ffccbc,stroke:#333
Prometheus 指标暴露:
const promClient = require('prom-client');
// 启用默认指标
promClient.collectDefaultMetrics();
// 自定义指标
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP 请求耗时',
labelNames: ['method', 'route', 'status'],
buckets: [0.1, 0.5, 1, 2, 5]
});
const httpRequestTotal = new promClient.Counter({
name: 'http_requests_total',
help: 'HTTP 请求总数',
labelNames: ['method', 'route', 'status']
});
// 中间件记录指标
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
const labels = {
method: req.method,
route: req.route?.path || req.path,
status: res.statusCode
};
httpRequestDuration.observe(labels, duration);
httpRequestTotal.inc(labels);
});
next();
});
// 暴露指标端点
app.get('/metrics', async (req, res) => {
res.set('Content-Type', promClient.register.contentType);
res.end(await promClient.register.metrics());
});
健康检查端点:
app.get('/health', async (req, res) => {
const health = {
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
checks: {}
};
// 检查数据库
try {
await db.query('SELECT 1');
health.checks.database = 'ok';
} catch (err) {
health.checks.database = 'error';
health.status = 'degraded';
}
// 检查 Redis
try {
await redis.ping();
health.checks.redis = 'ok';
} catch (err) {
health.checks.redis = 'error';
health.status = 'degraded';
}
const statusCode = health.status === 'ok' ? 200 : 503;
res.status(statusCode).json(health);
});
告警规则示例(Prometheus):
groups:
- name: node-app
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "高错误率告警"
description: "错误率超过 5%"
- alert: HighResponseTime
expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "响应时间过长"
description: "P95 响应时间超过 2 秒"
实战案例
RESTful API开发
RESTful 设计原则:
| 原则 | 说明 |
|---|---|
| 资源导向 | URL 表示资源,不是动作 |
| HTTP 方法语义 | GET 查询、POST 创建、PUT 更新、DELETE 删除 |
| 无状态 | 每个请求包含所有必要信息 |
| 统一接口 | 一致的 URL 和响应格式 |
| 分层系统 | 客户端不知道是否直连服务器 |
URL 设计规范:
| 操作 | HTTP 方法 | URL | 说明 |
|---|---|---|---|
| 获取列表 | GET | /api/users | 获取用户列表 |
| 获取单个 | GET | /api/users/:id | 获取指定用户 |
| 创建 | POST | /api/users | 创建用户 |
| 全量更新 | PUT | /api/users/:id | 更新用户 |
| 部分更新 | PATCH | /api/users/:id | 部分更新 |
| 删除 | DELETE | /api/users/:id | 删除用户 |
| 子资源 | GET | /api/users/:id/orders | 用户的订单 |
项目结构:
src/
├── app.js # 应用入口
├── config/ # 配置文件
│ └── index.js
├── controllers/ # 控制器
│ └── userController.js
├── middlewares/ # 中间件
│ ├── auth.js
│ ├── errorHandler.js
│ └── validator.js
├── models/ # 数据模型
│ └── User.js
├── routes/ # 路由
│ ├── index.js
│ └── userRoutes.js
├── services/ # 业务逻辑
│ └── userService.js
├── utils/ # 工具函数
│ └── response.js
└── validators/ # 验证规则
└── userValidator.js
统一响应格式:
// utils/response.js
class ApiResponse {
static success(res, data, message = 'Success', statusCode = 200) {
return res.status(statusCode).json({
success: true,
message,
data
});
}
static error(res, message, statusCode = 500, errors = null) {
return res.status(statusCode).json({
success: false,
message,
errors
});
}
static paginated(res, data, pagination) {
return res.status(200).json({
success: true,
data,
pagination: {
page: pagination.page,
limit: pagination.limit,
total: pagination.total,
totalPages: Math.ceil(pagination.total / pagination.limit)
}
});
}
}
错误处理中间件:
// middlewares/errorHandler.js
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
}
}
const errorHandler = (err, req, res, next) => {
err.statusCode = err.statusCode || 500;
if (process.env.NODE_ENV === 'development') {
return res.status(err.statusCode).json({
success: false,
message: err.message,
stack: err.stack
});
}
// 生产环境
if (err.isOperational) {
return res.status(err.statusCode).json({
success: false,
message: err.message
});
}
// 未知错误
console.error('ERROR:', err);
return res.status(500).json({
success: false,
message: '服务器内部错误'
});
};
分页查询实现:
// services/userService.js
async function getUsers(query) {
const page = parseInt(query.page) || 1;
const limit = parseInt(query.limit) || 10;
const skip = (page - 1) * limit;
const filter = {};
if (query.search) {
filter.$or = [
{ name: new RegExp(query.search, 'i') },
{ email: new RegExp(query.search, 'i') }
];
}
const [users, total] = await Promise.all([
User.find(filter).skip(skip).limit(limit).sort({ createdAt: -1 }),
User.countDocuments(filter)
]);
return { users, pagination: { page, limit, total } };
}
WebSocket实时通信
WebSocket vs HTTP:
| 特性 | HTTP | WebSocket |
|---|---|---|
| 连接方式 | 短连接 | 长连接 |
| 通信方向 | 单向(请求-响应) | 双向 |
| 实时性 | 轮询实现 | 原生支持 |
| 开销 | 每次请求带头部 | 握手后开销小 |
| 适用场景 | 普通 API | 实时应用 |
Socket.IO 实现:
const { Server } = require('socket.io');
const http = require('http');
const express = require('express');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: '*',
methods: ['GET', 'POST']
}
});
// 连接处理
io.on('connection', (socket) => {
console.log('用户连接:', socket.id);
// 加入房间
socket.on('join-room', (roomId) => {
socket.join(roomId);
socket.to(roomId).emit('user-joined', { userId: socket.id });
});
// 发送消息
socket.on('send-message', (data) => {
io.to(data.roomId).emit('new-message', {
userId: socket.id,
message: data.message,
timestamp: new Date()
});
});
// 断开连接
socket.on('disconnect', () => {
console.log('用户断开:', socket.id);
});
});
// 认证中间件
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
const user = verifyToken(token);
socket.user = user;
next();
} catch (err) {
next(new Error('认证失败'));
}
});
WebSocket 架构:
Redis Adapter(多实例支持):
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
io.adapter(createAdapter(pubClient, subClient));
});
微服务架构实践
微服务通信方式:
| 方式 | 说明 | 适用场景 |
|---|---|---|
| HTTP/REST | 同步调用 | 简单查询 |
| gRPC | 高性能 RPC | 内部服务通信 |
| 消息队列 | 异步通信 | 解耦、削峰 |
| 事件驱动 | 发布订阅 | 事件通知 |
微服务架构图:
Node.js"] end subgraph "微服务" B["用户服务"] C["订单服务"] D["商品服务"] E["支付服务"] end subgraph "基础设施" F["服务注册
Consul"] G["消息队列
RabbitMQ"] H["配置中心"] end A --> B A --> C A --> D A --> E B --> F C --> F D --> F E --> F B --> G C --> G E --> G style A fill:#ff9800,stroke:#333 style F fill:#4caf50,stroke:#333,color:#fff style G fill:#2196f3,stroke:#333,color:#fff
消息队列集成(RabbitMQ):
const amqp = require('amqplib');
class MessageQueue {
async connect() {
this.connection = await amqp.connect('amqp://localhost');
this.channel = await this.connection.createChannel();
}
// 发布消息
async publish(exchange, routingKey, message) {
await this.channel.assertExchange(exchange, 'topic', { durable: true });
this.channel.publish(
exchange,
routingKey,
Buffer.from(JSON.stringify(message)),
{ persistent: true }
);
}
// 订阅消息
async subscribe(exchange, queue, routingKey, handler) {
await this.channel.assertExchange(exchange, 'topic', { durable: true });
await this.channel.assertQueue(queue, { durable: true });
await this.channel.bindQueue(queue, exchange, routingKey);
this.channel.consume(queue, async (msg) => {
try {
const content = JSON.parse(msg.content.toString());
await handler(content);
this.channel.ack(msg);
} catch (err) {
this.channel.nack(msg, false, false);
}
});
}
}
// 使用示例
const mq = new MessageQueue();
await mq.connect();
// 发布订单创建事件
await mq.publish('orders', 'order.created', {
orderId: '123',
userId: '456',
amount: 100
});
// 订阅订单事件
await mq.subscribe('orders', 'payment-service', 'order.*', async (message) => {
console.log('收到订单事件:', message);
// 处理支付逻辑
});
服务发现(Consul):
const Consul = require('consul');
const consul = new Consul();
// 注册服务
async function registerService() {
await consul.agent.service.register({
name: 'user-service',
id: `user-service-${process.env.PORT}`,
address: 'localhost',
port: parseInt(process.env.PORT),
check: {
http: `http://localhost:${process.env.PORT}/health`,
interval: '10s'
}
});
}
// 发现服务
async function discoverService(serviceName) {
const services = await consul.health.service({
service: serviceName,
passing: true
});
if (services.length === 0) {
throw new Error(`服务 ${serviceName} 不可用`);
}
// 简单负载均衡
const index = Math.floor(Math.random() * services.length);
const service = services[index].Service;
return `http://${service.Address}:${service.Port}`;
}
分布式追踪:
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const provider = new NodeTracerProvider();
provider.addSpanProcessor(
new SimpleSpanProcessor(
new JaegerExporter({
endpoint: 'http://localhost:14268/api/traces'
})
)
);
provider.register();
const tracer = provider.getTracer('my-service');
// 创建 Span
const span = tracer.startSpan('operation-name');
try {
// 业务逻辑
span.setAttribute('user.id', userId);
} finally {
span.end();
}
常见问题与排查
内存泄漏排查
内存泄漏症状:
| 症状 | 说明 |
|---|---|
| 内存持续增长 | 重启后恢复,运行一段时间后又增长 |
| 响应变慢 | GC 频繁导致性能下降 |
| OOM 崩溃 | 内存超限被系统杀死 |
排查步骤:
生成堆快照:
// 方式1:使用 v8 模块
const v8 = require('v8');
const fs = require('fs');
function takeHeapSnapshot() {
const snapshotStream = v8.writeHeapSnapshot();
console.log('堆快照已保存:', snapshotStream);
}
// 方式2:使用 heapdump 模块
const heapdump = require('heapdump');
heapdump.writeSnapshot('./heap-' + Date.now() + '.heapsnapshot');
// 方式3:通过信号触发
process.on('SIGUSR2', () => {
heapdump.writeSnapshot();
});
使用 Chrome DevTools 分析:
- 启动应用:
node --inspect app.js - 打开 Chrome:
chrome://inspect - 点击 “inspect” 连接到应用
- 在 Memory 标签页生成快照
- 对比多个快照,找出增长的对象
常见内存泄漏模式:
| 模式 | 示例 | 解决方案 |
|---|---|---|
| 全局变量累积 | global.cache.push(data) | 使用 LRU 缓存 |
| 闭包引用 | 闭包持有大对象 | 及时释放引用 |
| 事件监听器 | 重复添加监听器 | 使用 once 或移除 |
| 定时器未清除 | setInterval 未清除 | clearInterval |
| Promise 未处理 | 大量 pending Promise | 添加错误处理 |
内存泄漏修复示例:
// ❌ 泄漏:闭包持有大对象
function processData() {
const largeData = loadLargeData();
return function() {
// largeData 永远不会被释放
console.log(largeData.length);
};
}
// ✅ 修复:只保留需要的数据
function processData() {
const largeData = loadLargeData();
const length = largeData.length; // 只保留需要的
return function() {
console.log(length);
};
}
// ❌ 泄漏:事件监听器累积
class MyClass {
constructor() {
emitter.on('event', this.handler); // 每次创建都添加
}
}
// ✅ 修复:移除监听器
class MyClass {
constructor() {
this.handler = this.handler.bind(this);
emitter.on('event', this.handler);
}
destroy() {
emitter.off('event', this.handler);
}
}
性能瓶颈定位
性能分析工具:
| 工具 | 用途 | 使用方式 |
|---|---|---|
| –prof | V8 性能分析 | node --prof app.js |
| clinic.js | 综合诊断 | clinic doctor -- node app.js |
| 0x | 火焰图 | 0x app.js |
| autocannon | 压力测试 | autocannon http://localhost:3000 |
使用 clinic.js:
# 安装
npm install -g clinic
# 诊断
clinic doctor -- node app.js
# 火焰图
clinic flame -- node app.js
# 事件循环分析
clinic bubbleprof -- node app.js
火焰图分析:
常见性能问题:
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 同步 I/O | 事件循环阻塞 | 改用异步 API |
| CPU 密集计算 | 响应延迟 | Worker Threads |
| 大量小对象 | GC 频繁 | 对象池 |
| 正则回溯 | CPU 100% | 优化正则表达式 |
| JSON 序列化 | 大对象慢 | 流式处理 |
| 数据库查询 | 响应慢 | 索引、缓存 |
事件循环延迟监控:
const { monitorEventLoopDelay } = require('perf_hooks');
const histogram = monitorEventLoopDelay({ resolution: 20 });
histogram.enable();
setInterval(() => {
const stats = {
min: histogram.min / 1e6,
max: histogram.max / 1e6,
mean: histogram.mean / 1e6,
p99: histogram.percentile(99) / 1e6
};
if (stats.p99 > 100) {
console.warn('事件循环延迟过高:', stats);
}
histogram.reset();
}, 5000);
常见错误处理
常见错误类型及解决:
| 错误 | 原因 | 解决方案 |
|---|---|---|
| ECONNREFUSED | 连接被拒绝 | 检查服务是否启动 |
| ECONNRESET | 连接被重置 | 检查网络、超时设置 |
| ETIMEDOUT | 连接超时 | 增加超时时间、检查网络 |
| EADDRINUSE | 端口被占用 | 更换端口或杀死占用进程 |
| ENOMEM | 内存不足 | 增加内存限制、优化代码 |
| EMFILE | 文件描述符耗尽 | 增加 ulimit、关闭未用连接 |
端口占用排查:
# 查找占用端口的进程
lsof -i :3000
# 杀死进程
kill -9 <PID>
# 或者使用 fuser
fuser -k 3000/tcp
文件描述符问题:
# 查看当前限制
ulimit -n
# 临时增加限制
ulimit -n 65535
# 永久修改(/etc/security/limits.conf)
* soft nofile 65535
* hard nofile 65535
未捕获异常处理:
// 捕获未处理的异常
process.on('uncaughtException', (err) => {
console.error('未捕获的异常:', err);
// 记录日志
logger.error('uncaughtException', { error: err.message, stack: err.stack });
// 优雅退出
gracefulShutdown();
});
// 捕获未处理的 Promise 拒绝
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的 Promise 拒绝:', reason);
logger.error('unhandledRejection', { reason });
});
// 优雅退出
async function gracefulShutdown() {
console.log('开始优雅退出...');
// 停止接收新请求
server.close();
// 等待现有请求完成
await new Promise(resolve => setTimeout(resolve, 5000));
// 关闭数据库连接
await db.close();
// 关闭 Redis 连接
await redis.quit();
console.log('退出完成');
process.exit(1);
}
// 监听退出信号
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
调试技巧:
| 技巧 | 说明 |
|---|---|
| DEBUG 环境变量 | DEBUG=app:* node app.js |
| –inspect | Chrome DevTools 调试 |
| console.trace() | 打印调用栈 |
| Error.captureStackTrace | 自定义错误栈 |
| source-map-support | TypeScript 源码映射 |
生产环境调试:
// 远程调试(谨慎使用)
// node --inspect=0.0.0.0:9229 app.js
// 按需开启调试
process.on('SIGUSR1', () => {
const inspector = require('inspector');
inspector.open(9229, '0.0.0.0');
console.log('调试器已开启');
// 30 秒后自动关闭
setTimeout(() => {
inspector.close();
console.log('调试器已关闭');
}, 30000);
});
错误追踪服务集成:
// Sentry 集成示例
const Sentry = require('@sentry/node');
Sentry.init({
dsn: 'https://[email protected]/xxx',
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1
});
// Express 集成
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());
// 路由...
app.use(Sentry.Handlers.errorHandler());
// 手动捕获错误
try {
riskyOperation();
} catch (err) {
Sentry.captureException(err);
throw err;
}
Playwright 自动化测试
Playwright 是微软开源的新一代自动化测试工具,支持 Chromium、Firefox 和 WebKit 浏览器,适用于端到端测试(E2E)。
核心特性
- 跨浏览器支持:一次编写,在所有现代浏览器中运行。
- 跨平台:支持 Windows、Linux、macOS。
- 多语言支持:支持 JavaScript/TypeScript、Python、Java、.NET。
- 自动等待:自动等待元素准备就绪,减少 flaky tests。
- 强大的工具链:包含代码生成器(Codegen)、调试器(Trace Viewer)。
安装与初始化
# 初始化项目
npm init playwright@latest
# 按提示选择:
# 1. TypeScript or JavaScript
# 2. Name of Tests folder (default: tests)
# 3. Add GitHub Actions workflow? (y/N)
# 4. Install Playwright browsers (can be done manually via 'npx playwright install')
基础使用
编写第一个测试 (tests/example.spec.ts):
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('https://playwright.dev/');
// 验证页面标题
await expect(page).toHaveTitle(/Playwright/);
});
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev/');
// 点击 Get started 链接
await page.getByRole('link', { name: 'Get started' }).click();
// 验证 URL 包含 intro
await expect(page).toHaveURL(/.*intro/);
});
常用操作
常用定位器 (Locators):
// 推荐使用面向用户的定位器
page.getByRole('button', { name: 'Submit' })
page.getByLabel('User Name')
page.getByPlaceholder('Enter your password')
page.getByText('Welcome')
page.getByTestId('submit-btn') // 需要在元素上添加 data-testid="submit-btn"
// CSS 选择器
page.locator('css=button.submit')
page.locator('#submit-btn')
常用断言 (Assertions):
await expect(locator).toBeVisible();
await expect(locator).toBeEnabled();
await expect(locator).toHaveText('Welcome');
await expect(locator).toHaveValue('123');
await expect(page).toHaveURL(/login/);
运行测试
# 运行所有测试
npx playwright test
# 运行特定测试文件
npx playwright test tests/example.spec.ts
# 带界面运行(UI 模式)
npx playwright test --ui
# 调试模式
npx playwright test --debug
# 查看测试报告
npx playwright show-report
常用技巧
录制测试脚本 (Codegen):
Playwright 提供了代码生成器,可以自动记录浏览器操作并生成测试代码。
# 启动录制并打开指定页面
npx playwright codegen playwright.dev
# 指定浏览器渠道(如使用本机安装的 Chrome)
npx playwright codegen --channel=chrome playwright.dev
# 将录制的代码保存到指定文件
npx playwright codegen -o tests/my-test.spec.ts playwright.dev
# 指定浏览器用户目录
npx playwright codegen \
--channel=chrome \
--user-data-dir="C:\Users\ralph\AppData\Local\Chrome-Automation"
Trace Viewer (调用栈查看):
配置 playwright.config.ts 开启 trace,可以在测试失败时查看完整的执行轨迹。
use: {
trace: 'on-first-retry', // 首次重试时记录
},
总结
Node.js 作为一个强大的服务端 JavaScript 运行时,具有以下核心优势:
| 优势 | 说明 |
|---|---|
| 高并发 | 事件驱动、非阻塞 I/O |
| 统一技术栈 | 前后端使用同一语言 |
| 丰富生态 | npm 拥有海量包 |
| 快速开发 | 轻量、灵活、迭代快 |
| 实时应用 | WebSocket 支持好 |
适用场景:
- I/O 密集型应用
- 实时通信应用
- API 服务
- 微服务架构
- 工具开发
不适用场景:
- CPU 密集型计算(可用 Worker Threads 缓解)
- 需要强类型的大型项目(可用 TypeScript)
掌握 Node.js 的核心概念(事件循环、异步编程、Stream)和最佳实践(错误处理、性能优化、安全),能够帮助开发者构建高质量的服务端应用。