Node.js 事件循环(Event Loop)

一、事件循环的作用与重要性

1.1 什么是事件循环

事件循环是Node实现非阻塞I/O操作的核心机制,它可以允许Node.js在单线程环境下处理大量并发操作

1.2 核心作用

  • 非阻塞I/O处理:让单线程的JavaScript能够处理高并发请求

  • 事件驱动架构:基于回调函数响应各种事件

  • 资源高效利用:避免为每个连接创建新线程的开销

二、事件循环的阶段详解

2.1 事件循环的六个阶段

bash 复制代码
// 事件循环阶段示意图
   ┌───────────────────────────┐
┌─>│           timers          │ // 执行setTimeout和setInterval回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │ // 执行系统操作的回调(如TCP错误)
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │ // 内部使用
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           poll            │ // 检索新的I/O事件,执行相关回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           check           │ // 执行setImmediate回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │ // 执行关闭事件的回调(如socket.close)
   └───────────────────────────┘

2.2 代码示例 -> 各阶段详细说明

javascript 复制代码
const fs = require('fs');

// 1. timers阶段
console.log('开始');

setTimeout(() => {
  console.log('setTimeout - timers阶段');
}, 0);

// 2. poll阶段
fs.readFile(__filename, () => {
  console.log('fs.readFile - poll阶段');
  
  // 3. check阶段
  setImmediate(() => {
    console.log('setImmediate - check阶段');
  });
});

// 4. nextTick队列(微任务)
process.nextTick(() => {
  console.log('process.nextTick - 微任务');
});

console.log('结束');

三、实际应用场景

3.1 高性能Web服务器

javascript 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
  // 模拟异步数据库查询
  setImmediate(() => {
    // 在check阶段执行,避免阻塞其他请求
    const userData = { id: 1, name: '张三' };
    
    process.nextTick(() => {
      // 确保在发送响应前完成所有同步操作
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify(userData));
    });
  });
});

server.listen(3000, () => {
  console.log('服务器运行在端口3000');
});

3.2 批量数据处理

javascript 复制代码
class BatchProcessor {
  constructor(batchSize = 100) {
    this.batchSize = batchSize;
    this.queue = [];
    this.processing = false;
  }
  
  add(data) {
    this.queue.push(data);
    
    if (!this.processing) {
      this.processing = true;
      // 使用setImmediate而不是setTimeout,更高效
      setImmediate(() => this.processBatch());
    }
  }
  
  processBatch() {
    const batch = this.queue.splice(0, this.batchSize);
    
    if (batch.length > 0) {
      // 处理当前批次
      this.processItems(batch);
      
      // 使用nextTick确保递归调用不会导致栈溢出
      process.nextTick(() => this.processBatch());
    } else {
      this.processing = false;
    }
  }
  
  processItems(items) {
    // 模拟数据处理
    items.forEach(item => {
      console.log('处理项目:', item);
    });
  }
}

// 使用示例
const processor = new BatchProcessor();
for (let i = 0; i < 1000; i++) {
  processor.add(`数据-${i}`);
}

四、实战技巧与最佳实践

4.1 避免阻塞事件循环

javascript 复制代码
// ❌ 错误示范:CPU密集型任务会阻塞事件循环
app.get('/slow', (req, res) => {
  let result = 0;
  for (let i = 0; i < 1000000000; i++) {
    result += i; // 这会阻塞事件循环
  }
  res.json({ result });
});

// ✅ 正确做法:分解任务或使用工作线程
app.get('/fast', (req, res) => {
  // 使用setImmediate分解任务
  let result = 0;
  let i = 0;
  
  function calculateChunk() {
    for (let j = 0; j < 1000000; j++) {
      if (i >= 1000000000) {
        res.json({ result });
        return;
      }
      result += i;
      i++;
    }
    
    // 让出事件循环,处理其他任务
    setImmediate(calculateChunk);
  }
  
  calculateChunk();
});

4.2 合理的任务调度

javascript 复制代码
class TaskScheduler {
  constructor() {
    this.tasks = [];
    this.isRunning = false;
  }
  
  addTask(task, priority = 'normal') {
    this.tasks.push({ task, priority });
    this.schedule();
  }
  
  schedule() {
    if (this.isRunning) return;
    
    this.isRunning = true;
    
    const execute = () => {
      if (this.tasks.length === 0) {
        this.isRunning = false;
        return;
      }
      
      // 按优先级排序
      this.tasks.sort((a, b) => {
        const priorityOrder = { high: 0, normal: 1, low: 2 };
        return priorityOrder[a.priority] - priorityOrder[b.priority];
      });
      
      const currentTask = this.tasks.shift().task;
      
      // 使用nextTick确保同步代码执行完毕
      process.nextTick(() => {
        try {
          currentTask();
        } catch (error) {
          console.error('任务执行错误:', error);
        }
        
        // 使用setImmediate让出控制权
        setImmediate(execute);
      });
    };
    
    execute();
  }
}

// 使用示例
const scheduler = new TaskScheduler();
scheduler.addTask(() => console.log('高优先级任务'), 'high');
scheduler.addTask(() => console.log('普通任务'), 'normal');

4.3 错误处理与监控

javascript 复制代码
// 监控事件循环延迟
class EventLoopMonitor {
  constructor() {
    this.lastTime = process.hrtime();
    this.delays = [];
    this.startMonitoring();
  }
  
  startMonitoring() {
    setInterval(() => {
      const diff = process.hrtime(this.lastTime);
      const nanosec = diff[0] * 1e9 + diff[1];
      const delay = nanosec - 1e9; // 期望1秒间隔
      
      if (delay > 0) {
        this.delays.push(delay);
        if (this.delays.length > 100) this.delays.shift();
        
        const avgDelay = this.delays.reduce((a, b) => a + b) / this.delays.length;
        
        if (avgDelay > 1e7) { // 10ms平均延迟
          console.warn('事件循环延迟警告:', Math.round(avgDelay / 1e6) + 'ms');
        }
      }
      
      this.lastTime = process.hrtime();
    }, 1000);
  }
}

// 全局错误处理
process.on('uncaughtException', (error) => {
  console.error('未捕获的异常:', error);
  // 优雅关闭
  process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的Promise拒绝:', reason);
});

五、高级应用场景

5.1 实时数据处理管道

javascript 复制代码
class DataProcessingPipeline {
  constructor() {
    this.stages = [];
    this.buffer = [];
    this.processing = false;
  }
  
  addStage(stage) {
    this.stages.push(stage);
  }
  
  push(data) {
    this.buffer.push(data);
    this.process();
  }
  
  async process() {
    if (this.processing) return;
    
    this.processing = true;
    
    while (this.buffer.length > 0) {
      const data = this.buffer.shift();
      
      try {
        let result = data;
        
        for (const stage of this.stages) {
          // 每个阶段使用setImmediate避免阻塞
          await new Promise((resolve) => {
            setImmediate(async () => {
              result = await stage(result);
              resolve();
            });
          });
        }
        
        console.log('处理结果:', result);
      } catch (error) {
        console.error('处理错误:', error);
      }
    }
    
    this.processing = false;
  }
}

// 使用示例
const pipeline = new DataProcessingPipeline();

pipeline.addStage(async (data) => {
  // 数据验证阶段
  return { ...data, validated: true };
});

pipeline.addStage(async (data) => {
  // 数据转换阶段
  return { ...data, transformed: true };
});

// 推送数据
for (let i = 0; i < 10; i++) {
  pipeline.push({ id: i, value: Math.random() });
}

5.2 资源池管理

javascript 复制代码
class ConnectionPool {
  constructor(maxConnections = 10) {
    this.maxConnections = maxConnections;
    this.activeConnections = 0;
    this.waitingQueue = [];
  }
  
  async getConnection() {
    return new Promise((resolve) => {
      const tryAcquire = () => {
        if (this.activeConnections < this.maxConnections) {
          this.activeConnections++;
          resolve({
            release: () => {
              this.activeConnections--;
              this.processQueue();
            }
          });
        } else {
          this.waitingQueue.push(tryAcquire);
        }
      };
      
      // 使用nextTick避免同步递归
      process.nextTick(tryAcquire);
    });
  }
  
  processQueue() {
    if (this.waitingQueue.length > 0 && this.activeConnections < this.maxConnections) {
      const nextRequest = this.waitingQueue.shift();
      setImmediate(nextRequest);
    }
  }
}

// 使用示例
const pool = new ConnectionPool(3);

async function useConnection(id) {
  const connection = await pool.getConnection();
  console.log(`连接 ${id} 获取成功`);
  
  // 模拟使用连接
  setTimeout(() => {
    connection.release();
    console.log(`连接 ${id} 已释放`);
  }, 1000);
}

// 模拟并发请求
for (let i = 0; i < 10; i++) {
  useConnection(i);
}

六、性能优化技巧

6.1 选择合适的定时器

javascript 复制代码
// 根据需求选择合适的调度方法
function scheduleTask(task, options = {}) {
  const { type = 'immediate', delay = 0 } = options;
  
  switch (type) {
    case 'nextTick':
      // 最高优先级,在当前操作完成后立即执行
      process.nextTick(task);
      break;
      
    case 'immediate':
      // 在check阶段执行,适合I/O操作后
      setImmediate(task);
      break;
      
    case 'timeout':
      // 在timers阶段执行,适合需要延迟的任务
      setTimeout(task, delay);
      break;
      
    default:
      setImmediate(task);
  }
}

6.2 批量操作优化

javascript 复制代码
// 批量处理I/O操作
async function batchIOOperations(operations) {
  const results = [];
  const batchSize = 10;
  
  for (let i = 0; i < operations.length; i += batchSize) {
    const batch = operations.slice(i, i + batchSize);
    
    // 使用Promise.all并行处理,但通过setImmediate控制并发
    const batchResults = await new Promise((resolve) => {
      setImmediate(async () => {
        const promises = batch.map(op => op());
        resolve(await Promise.all(promises));
      });
    });
    
    results.push(...batchResults);
    
    // 让出事件循环,避免阻塞
    if (i + batchSize < operations.length) {
      await new Promise(resolve => setImmediate(resolve));
    }
  }
  
  return results;
}
相关推荐
勤奋菲菲19 小时前
Egg.js 完全指南:企业级 Node.js 应用框架
开发语言·javascript·node.js
aesthetician21 小时前
Node.js 24.10.0: 拥抱现代 JavaScript 与增强性能
开发语言·javascript·node.js
APItesterCris1 天前
Node.js/Python 实战:编写一个淘宝商品数据采集器
大数据·开发语言·数据库·node.js
勤奋菲菲2 天前
Koa.js 完全指南:下一代 Node.js Web 框架
前端·javascript·node.js
少年阿闯~~2 天前
Node.js核心模块:fs、path与http详解
node.js
teeeeeeemo2 天前
Webpack 模块联邦(Module Federation)
开发语言·前端·javascript·笔记·webpack·node.js
全马必破三2 天前
Node.js HTTP开发
网络协议·http·node.js
千叶寻-2 天前
正则表达式
前端·javascript·后端·架构·正则表达式·node.js
无责任此方_修行中3 天前
谁动了我的数据?一个 Bug 背后的“一行代码”真凶
后端·node.js·debug