一、Node.js 核心特点
| 核心特点 | 说明 |
|---|---|
| 单线程 | Node.js 使用单线程处理请求 |
| 事件循环 | 通过事件驱动机制处理并发 |
| 非阻塞 I/O | I/O 操作不会阻塞主线程 |
| 跨平台 | 可以在 Windows、Linux、macOS 等系统上运行 |
Node.js 通过 V8 引擎 执行 JavaScript 代码,使用 Node.js API 与操作系统交互,并通过 Libuv 处理异步 I/O 操作。事件循环和工作线程确保了 Node.js 的高效和非阻塞特性。
二、Node.js 架构组成
Node.js 的架构可以分为以下几个主要层次:
1. JavaScript 层
这是开发者直接接触的层面,包括:
-
核心模块(如
fs、http、path等) -
第三方模块(通过 npm 安装)
-
用户自定义模块
2. C++ 绑定层
这一层将底层功能暴露给 JavaScript 层,包括:
-
Node.js 核心 API 的 C++ 实现
-
V8 引擎的接口封装
3. 底层依赖
| 依赖 | 说明 |
|---|---|
| V8 引擎 | Google 开发的 JavaScript 引擎,负责执行 JS 代码,将代码编译成机器码 |
| Libuv | 跨平台的异步 I/O 库,处理文件系统、网络和进程等异步操作 |
| c-ares | 异步 DNS 解析库 |
| OpenSSL | 加密功能支持 |
| zlib | 压缩功能支持 |
三、Node.js 架构图
┌─────────────────────────────────────────────────────────┐ │ JavaScript 层 │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │ │ 核心模块 │ │ 第三方模块 │ │ 用户自定义模块 │ │ │ │ (fs, http) │ │ (npm 安装) │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ ├─────────────────────────────────────────────────────────┤ │ C++ 绑定层 │ │ ┌─────────────────────────────────────────────────┐ │ │ │ Node.js Bindings (Node API) │ │ │ │ 文件系统 │ 网络 │ 进程 │ 其他操作 │ │ │ └─────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────┤ │ 底层依赖 │ │ ┌───────────┐ ┌───────────┐ ┌─────────────────────┐ │ │ │ V8 │ │ libuv │ │ c-ares / OpenSSL │ │ │ │ 引擎 │ │ 异步 I/O │ │ / zlib │ │ │ └───────────┘ └───────────┘ └─────────────────────┘ │ └─────────────────────────────────────────────────────────┘
四、事件循环机制
事件循环是 Node.js 实现非阻塞 I/O 的核心机制,它负责调度和执行所有异步操作。
事件循环的阶段
事件循环按照固定顺序依次处理以下几个阶段:
┌───────────────────────────┐ ┌─>│ timers │ ← 执行 setTimeout、setInterval 的到期回调 │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ ← 执行系统操作的回调(如 TCP 错误) │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ ← 内部使用 │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ poll │ ← 检索新的 I/O 事件,执行相关回调 │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ check │ ← 执行 setImmediate 的回调 │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ ← 执行关闭事件的回调(如 socket.on('close')) └───────────────────────────┘
各阶段说明:
| 阶段 | 说明 |
|---|---|
| timers | 执行 setTimeout 和 setInterval 的到期回调 |
| pending callbacks | 执行系统操作的回调(如 TCP 错误) |
| idle, prepare | Node.js 内部使用 |
| poll | 检索新的 I/O 事件,执行相关回调(核心阶段) |
| check | 执行 setImmediate 的回调 |
| close callbacks | 执行关闭事件的回调(如 socket.on('close')) |
事件循环示例
// 示例:理解事件循环顺序 setTimeout(() => console.log('timeout'), 0); setImmediate(() => console.log('immediate')); // 输出顺序可能不同,取决于事件循环的启动时间
// 更完整的示例 const fs = require('fs'); console.log('1. 同步代码开始'); setTimeout(() => console.log('2. setTimeout'), 0); setImmediate(() => console.log('3. setImmediate')); fs.readFile(__filename, () => { console.log('4. 文件读取完成'); }); console.log('5. 同步代码结束'); // 可能的输出顺序: // 1. 同步代码开始 // 5. 同步代码结束 // 3. setImmediate // 2. setTimeout // 4. 文件读取完成
五、非阻塞 I/O 原理
Node.js 的 I/O 操作是非阻塞的,这是通过以下方式实现的:
工作流程
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 应用发起 │───▶│ Node.js │───▶│ libuv │ │ I/O 请求 │ │ 处理请求 │ │ 异步处理 │ └─────────────┘ └─────────────┘ └─────────────┘ │ ┌─────────────┐ ┌─────────────┐ │ │ 事件循环 │◀───│ I/O 完成 │◀───────────┘ │ 执行回调 │ │ 回调入队 │ └─────────────┘ └─────────────┘
工作流程说明:
-
应用发起 I/O 请求(如读取文件)
-
Node.js 将请求交给 libuv 处理
-
libuv 使用系统提供的异步接口(如 Linux 的 epoll)
-
主线程继续执行其他任务
-
I/O 完成后,回调函数被放入事件队列
-
事件循环在适当阶段执行回调
六、单线程与多进程
虽然 Node.js 的 JavaScript 执行是单线程的,但它可以通过以下方式利用多核 CPU:
1. 子进程(child_process)
const { fork } = require('child_process'); const child = fork('child.js'); child.on('message', (msg) => { console.log('来自子进程的消息:', msg); }); child.send({ hello: 'world' });
2. 集群模式(cluster)
const cluster = require('cluster'); const http = require('http'); const numCPUs = require('os').cpus().length; if (cluster.isMaster) { // 主进程 fork 工作进程 for (let i = 0; i < numCPUs; i++) { cluster.fork(); } } else { // 工作进程创建 HTTP 服务器 http.createServer((req, res) => { res.writeHead(200); res.end('你好世界\n'); }).listen(8000); }
3. Worker Threads(工作线程)
const { Worker } = require('worker_threads'); const worker = new Worker(` const { parentPort } = require('worker_threads'); parentPort.on('message', (msg) => { console.log('收到消息:', msg); parentPort.postMessage('消息已收到'); }); `, { eval: true }); worker.on('message', (msg) => { console.log('来自工作线程的回复:', msg); }); worker.postMessage('主线程消息');
七、性能优化建议
1. 避免阻塞事件循环
-
将 CPU 密集型任务分流到工作线程或子进程
-
避免在主线程进行复杂计算
2. 合理使用流处理
// 不好的做法:一次性读取大文件 fs.readFile('bigfile.txt', (err, data) => { // 处理数据 }); // 好的做法:使用流 const stream = fs.createReadStream('bigfile.txt'); stream.on('data', (chunk) => { // 处理数据块 });
3. 连接池管理
-
数据库连接使用连接池
-
HTTP 客户端连接复用
4. 内存管理
-
监控内存使用情况
-
避免内存泄漏
八、常见问题解答
| 问题 | 答案 |
|---|---|
| Node.js 真的是单线程吗? | JavaScript 执行是单线程的,但 Node.js 底层使用了多线程(如 libuv 的线程池处理某些 I/O 操作) |
| 如何处理 CPU 密集型任务? | 可以使用 Worker Threads,或将任务拆分为多个小任务,通过 setImmediate 分批处理 |
| 为什么 Node.js 适合 I/O 密集型应用? | 因为它的非阻塞 I/O 模型可以在等待 I/O 时处理其他请求,而不需要为每个连接创建新线程 |
九、本章小结
| 知识点 | 说明 |
|---|---|
| Node.js 架构 | JavaScript 层 → C++ 绑定层 → 底层依赖(V8、libuv 等) |
| 事件循环 | 6 个阶段依次执行,处理各类异步回调 |
| 非阻塞 I/O | 通过 libuv 实现,主线程不会被 I/O 操作阻塞 |
| 单线程本质 | JS 执行是单线程的,但底层使用多线程处理 I/O |
| 多核利用 | 通过 cluster 模块、Worker Threads 或子进程 |
| 优化建议 | 避免阻塞事件循环、使用流处理、连接池管理、监控内存 |
核心理解:Node.js 的工作机制基于事件驱动的编程模型和非阻塞 I/O 操作。虽然 JavaScript 执行是单线程的,但通过事件循环和底层多线程 I/O 处理,Node.js 能够高效处理高并发场景。理解这些机制对于编写高性能的 Node.js 应用至关重要。