Nodejs 是如何实现高并发?

292 阅读5分钟

原理:Node.js 的多并发事件机制是基于其底层的 事件循环(Event Loop)异步 I/O 模型实现的。以下是它的基本工作原理和关键概念:

1. 单线程事件驱动模型

Node.js 是单线程的,但它通过事件循环和异步 I/O 能够处理大量的并发请求。尽管 Node.js 在一个线程上运行,它依赖操作系统提供的异步 I/O 操作和多线程能力来处理并发任务。

2. 事件循环(Event Loop)

事件循环是 Node.js 并发处理的核心。它是一个无限循环,负责检查事件队列中的任务并处理它们。事件循环的工作流程可以简化为以下几个步骤:

  1. 计时器阶段(Timers): 处理通过 setTimeoutsetInterval 设置的定时器回调。

  2. 待定回调阶段(Pending Callbacks): 处理一些系统操作的回调,这些操作通常由操作系统提供。

  3. 空闲阶段(Idle, Prepare): 仅供 Node.js 内部使用,用户代码通常不会涉及。

  4. 轮询阶段(Polling): 事件循环等待并检查 I/O 操作是否已完成。如果有完成的 I/O 操作,相关的回调函数将被添加到回调队列中。

  5. 检查阶段(Check): 执行 setImmediate 的回调函数。

  6. 关闭回调阶段(Close Callbacks): 处理一些关闭事件的回调,如socket.on('close', ...)

3. 异步 I/O 操作

当 Node.js 发起一个 I/O 操作(如文件读取、网络请求),它不会阻塞主线程,而是将该操作交给操作系统处理。一旦操作系统完成了 I/O 操作,它会将结果返回给事件循环,事件循环会将相应的回调函数放入回调队列,等待处理。

4. 并发控制

尽管 Node.js 是单线程的,它能够处理并发 I/O 操作。这是因为 I/O 操作由操作系统的多线程库(如 libuv)或其他异步 API 处理,而不是由 Node.js 主线程处理。这意味着 Node.js 可以在等待 I/O 操作完成的同时继续处理其他任务。

5. 线程池(Thread Pool)

Node.js 的一些 I/O 操作(如文件系统操作)是使用 libuv 库的线程池来处理的。libuv 是一个跨平台的异步 I/O 库,Node.js 通过它来实现异步 I/O 操作。线程池通常由 4 个线程组成(可以通过 UV_THREADPOOL_SIZE 环境变量配置),这些线程用于处理那些无法通过异步事件完成的任务,如文件系统操作。

6. 事件驱动与回调函数

事件驱动模型依赖于回调函数来处理异步任务。当一个异步操作完成时,Node.js 将调用相应的回调函数。通过这种方式,Node.js 可以高效地处理大量并发任务,而不需要等待某个操作完成后再继续执行其他操作。

7.事件驱动源码解读

高并发是Node.js最大的优势之一,而大部分的 Node.js 核心 API 都是基于异步事件驱动的体系结构所实现的。EventEmitter 是 Node.js 中用于事件驱动编程的核心模块,它允许对象发布事件并注册事件处理程序。

原理:事件驱动基于内存中的数据结构(对象和数组)来管理事件和监听器,并通过事件循环机制来处理异步操作,其核心EventEmitter本质上就是一个发布订阅。

EventEmitter 类的实现大致如下(简化版)

// EventEmitter.js

class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
    return this;
  }

  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach(listener => listener(...args));
    }
    return this;
  }

  off(event, listener) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(l => l !== listener);
    }
    return this;
  }

  once(event, listener) {
    const wrapper = (...args) => {
      listener(...args);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
    return this;
  }
}

module.exports = EventEmitter;

  1. EventEmitter 使用一个对象 (this.events) 来存储事件及其监听器。每个事件名作为对象的键,监听器数组作为值。

  2. 当触发事件时,emit 方法会遍历事件名对应的所有监听器,并依次调用它们。这是一种简单的“发布-订阅”模式的实现。

  3. 需要注意的是EventEmitter 本身并不处理异步操作。异步事件通常通过回调函数的方式处理,EventEmitter 本身的设计还是基于同步调用。异步操作需要使用 setImmediateprocess.nextTick 将回调函数放入事件循环中处理。

  4. EventEmitter还有许多其他高级特性,如defaultMaxListeners。EventEmitter 有一个默认的最大监听器数量限制,以防止内存泄漏。可以通过 setMaxListenersgetMaxListeners 方法设置和获取此值。其他的高级特性就不一一展开介绍了。

8. 并发场景

多并发事件机制应用场景广泛,比如:

1) 高并发网络服务器: 处理大量的并发网络请求,如 Web 服务器。

2) 实时应用: 实现实时聊天、在线游戏等应用。

3)大数据读写: 处理大规模的数据读写操作,并发执行多个数据库查询。

Node.js 的多并发事件机制依赖于事件循环和异步 I/O 模型。

尽管 Node.js 是单线程的,它通过事件循环来处理并发任务,并依赖于操作系统的异步 I/O 能力来实现高并发的处理能力,这使得 Node.js 在处理 I/O 密集型应用(如网络服务器)时表现非常出色~