Skip to content

Node.js 异步处理机制详解

你的问题解答

问题:Express接口需要3秒返回,后面的请求会阻塞吗?

答案:不会阻塞! 这正是Node.js的核心优势。

Node.js 如何处理并发请求

1. 事件循环 (Event Loop)

Node.js虽然是单线程的,但它使用了事件循环机制:

   ┌───────────────────────────┐
┌─>│           timers          │  ← setTimeout, setInterval
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │  ← I/O回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │  ← 内部使用
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           poll            │  ← 获取新的I/O事件
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           check           │  ← setImmediate回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │  ← 关闭回调
   └───────────────────────────┘

2. 非阻塞I/O原理

当一个请求到达时:

  1. 主线程接收请求
  2. 如果是异步操作(如setTimeout、数据库查询、文件读取)
  3. 将操作委托给线程池或系统
  4. 主线程继续处理下一个请求
  5. 异步操作完成后,回调被放入事件队列
  6. 事件循环检查队列,执行回调

3. 实际执行流程

javascript
// 当请求1到达 /slow 接口
app.get('/slow', async (req, res) => {
    console.log('请求1开始处理');
    // setTimeout 不会阻塞主线程
    await new Promise(resolve => setTimeout(resolve, 3000));
    console.log('请求1处理完成');
    res.json({message: '完成'});
});

// 在请求1等待的3秒内,请求2到达 /fast 接口
app.get('/fast', (req, res) => {
    console.log('请求2立即处理'); // 立即执行,不等待请求1
    res.json({message: '快速响应'});
});

关键概念解释

单线程 vs 多线程处理

传统多线程模型(如Apache):

  • 每个请求分配一个线程
  • 线程阻塞等待I/O操作
  • 内存消耗大,上下文切换开销大

Node.js单线程模型:

  • 主线程处理所有请求
  • I/O操作异步执行
  • 内存消耗小,无上下文切换

线程池的作用

Node.js虽然主线程是单线程,但底层使用了libuv线程池

  • 文件系统操作
  • DNS查询
  • 某些CPU密集型操作

这些操作在后台线程池中执行,不阻塞主线程。

性能优势

内存使用对比

Apache (多线程):
- 每个线程: ~2MB
- 1000个并发: ~2GB内存

Node.js (单线程):
- 主线程: ~10MB
- 1000个并发: ~50MB内存

适用场景

Node.js适合:

  • I/O密集型应用(API服务、Web应用)
  • 实时应用(聊天、游戏)
  • 微服务架构

Node.js不适合:

  • CPU密集型计算
  • 大量同步操作

测试验证

运行提供的示例代码:

  1. 启动服务器:node express-demo.js
  2. 运行并发测试:node test-concurrent.js

你会看到:

  • 快接口立即返回
  • 慢接口并发处理
  • 总体响应时间优化

总结

Node.js通过事件循环非阻塞I/O实现了高并发处理能力。虽然是单线程,但通过异步机制避免了阻塞,这就是为什么Node.js能够用较少的资源处理大量并发请求的原因。