Skip to content

Node.js 并发处理机制详解

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

答案:不会阻塞! Node.js 使用事件循环机制,支持非阻塞并发处理。

🔍 核心概念

1. 事件循环 (Event Loop)

Node.js 是单线程的,但通过事件循环实现并发:

javascript
// 伪代码展示事件循环工作原理
while (eventLoop.hasEvents()) {
    const event = eventLoop.getNextEvent();
    
    if (event.isAsync()) {
        // 异步操作交给系统处理,不阻塞主线程
        system.handleAsync(event, callback);
    } else {
        // 同步操作立即执行
        event.execute();
    }
}

2. 非阻塞 I/O

当遇到耗时操作时:

  • 阻塞式:线程等待操作完成
  • 非阻塞式:操作交给系统,线程继续处理其他请求

🧪 实际演示

场景模拟

javascript
// 慢接口 - 需要3秒处理时间
app.get('/slow', async (req, res) => {
    console.log('慢接口开始处理');
    
    // 这里的 setTimeout 是异步的,不会阻塞其他请求
    await new Promise(resolve => setTimeout(resolve, 3000));
    
    console.log('慢接口处理完成');
    res.json({ message: '慢接口响应' });
});

// 快接口 - 立即响应
app.get('/fast', (req, res) => {
    console.log('快接口立即响应');
    res.json({ message: '快接口响应' });
});

并发测试结果

当同时发起多个请求时:

时间轴演示:
0ms:  请求1(/slow) 开始处理
10ms: 请求2(/fast) 立即响应 ✅
20ms: 请求3(/slow) 开始处理
30ms: 请求4(/fast) 立即响应 ✅
3000ms: 请求1(/slow) 完成响应 ✅
3020ms: 请求3(/slow) 完成响应 ✅

🔧 关键技术点

1. 异步操作类型

javascript
// 这些操作都是非阻塞的:
- setTimeout/setInterval
- 文件读写 (fs.readFile)
- 数据库查询
- HTTP 请求
- Promise/async-await

2. 事件循环阶段

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

3. 线程池

某些操作使用线程池处理:

  • 文件系统操作
  • DNS 查询
  • CPU 密集型任务

📊 性能对比

传统多线程模型

请求1 → 线程1 (阻塞3秒)
请求2 → 线程2 (阻塞3秒)
请求3 → 线程3 (阻塞3秒)
...
资源消耗:每个线程 ~2MB 内存

Node.js 事件循环模型

请求1 → 事件循环 → 异步处理 (非阻塞)
请求2 → 事件循环 → 立即响应
请求3 → 事件循环 → 异步处理 (非阻塞)
...
资源消耗:单线程 + 事件循环

⚠️ 注意事项

1. CPU 密集型任务会阻塞

javascript
// ❌ 这会阻塞事件循环
app.get('/cpu-intensive', (req, res) => {
    let result = 0;
    for (let i = 0; i < 10000000000; i++) {
        result += i; // 同步计算,会阻塞
    }
    res.json({ result });
});

// ✅ 正确做法:使用 Worker Threads 或分批处理
const { Worker } = require('worker_threads');

app.get('/cpu-intensive-async', (req, res) => {
    const worker = new Worker('./cpu-worker.js');
    worker.postMessage({ task: 'heavy-calculation' });
    
    worker.on('message', (result) => {
        res.json({ result });
    });
});

2. 回调地狱和错误处理

javascript
// ❌ 回调地狱
app.get('/callback-hell', (req, res) => {
    db.query('SELECT * FROM users', (err1, users) => {
        if (err1) throw err1;
        db.query('SELECT * FROM posts', (err2, posts) => {
            if (err2) throw err2;
            // 更多嵌套...
        });
    });
});

// ✅ 使用 async/await
app.get('/clean-async', async (req, res) => {
    try {
        const users = await db.query('SELECT * FROM users');
        const posts = await db.query('SELECT * FROM posts');
        res.json({ users, posts });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

🎯 总结

  1. Node.js 不会因为单个慢接口而阻塞其他请求
  2. 事件循环机制实现高并发处理
  3. 异步 I/O 操作是关键
  4. 避免 CPU 密集型同步操作
  5. 合理使用 async/await 处理异步流程

🚀 最佳实践

javascript
// 1. 使用异步操作
const data = await database.query('SELECT * FROM table');

// 2. 设置合理的超时
const timeout = setTimeout(() => {
    res.status(408).json({ error: 'Request timeout' });
}, 30000);

// 3. 错误处理
try {
    const result = await someAsyncOperation();
    clearTimeout(timeout);
    res.json(result);
} catch (error) {
    clearTimeout(timeout);
    res.status(500).json({ error: error.message });
}

// 4. 使用连接池
const pool = mysql.createPool({
    connectionLimit: 10,
    host: 'localhost',
    // ...
});

这就是为什么 Node.js 特别适合 I/O 密集型应用,如 Web API、实时应用等场景!