Skip to content

NodeJS 为何性能高

NodeJS 是基于 javascript 语言,构建在 Google V8 引擎上,基于事件驱动、非阻塞 I/O 模型。虽然是单进程单线程,但是基于事件驱动模型,nodejs 的性能非常高 (高并发)。

事件驱动模型

非阻塞式模型,当有新的请求或任务到达服务器时,服务器进行响应,利用异步处理事件的能力,程序无需等待结果返回,而是基于回调通知机制,将应答结果在随后的事件回调中返回给请求者,原本同步模式下需要等待的时间,则可以用来处理其他任务。因此高效!(比如餐馆点餐)

进程:资源分配的最小单位; 线程:运算调度的最小单位;

线程隶属于进程,一个进程可以拥有多个线程;一个线程只能隶属于一个进程。

单线程

一个进程只开辟了一个线程,js 属于单线程,通过『异步操作』缓解顺序执行时带来的线程阻塞问题。

多进程

在多核心 cpu 的服务器上,可以通过 child_process.forkCluster 来启动多个进程,提高 CPU 利用率。(⚠️ 不是为了解决高并发问题)

child_process.fork 开启多进程

js
// child_process.js 子进程任务
function computeSum() {
  console.time("computeTime");
  let sum = 0;
  for (let i = 0; i < 1e10; i++) {
    sum += i;
  }

  console.timeEnd("computeTime");
  return sum;
}

// 接收:主进程下发的任务通知
process.on("message", (msg) => {
  console.log(msg, "process.pid:", process.pid);

  // 执行大量运算任务
  let res = computeSum();

  // 任务执行完毕,通知主进程!
  process.send(res);
});
js
// fork_server.js 主进程
let http = require("http");
let { fork } = require("child_process");

let server = http.createServer((req, response) => {
  if (req.url == "/compute") {
    console.log("on compute");

    // 开启:「大量计算任务」子进程
    let childProcess = fork("./child_process.js");

    // 通知:子进程开始执行任务
    childProcess.send("开启一个新的子进程,用于大计算。");

    // 接收:子进程反馈的执行结果消息!
    childProcess.on("message", (res) => {
      console.log("res:", res);

      response.end(`compute result is: ${res}`);

      // 结束:子进程
      childProcess.kill();
    });
  } else {
    response.end(`ok`);
  }
});

server.listen(3333, () => {
  console.log("server running at http://127.0.0.1:3333");
});

Cluster 开启多进程

通过 cluster.isMaster 判断是否在主进程,如果是,则根据 cpu 数量调用 cluster.fork() 方法创建多个子进程。如果不是,则代表当前处在工作(worker)进程中,则进行具体任务处理。

js
let http = require("http");
let cluster = require("cluster");
let cpus = require("os").cpus();
process.title = "node-master";

// 如果是主进程,执行「创建子进程」的逻辑
if (cluster.isMaster) {
  console.log(`cups ${cpus.length}`);
  console.log("master");
  // 根据主机cpu数量创建子进程(一般为cpu数量的一半,为了平衡性能和cpu占用率)
  for (let i = 0; i < i < (Math.floor(cpus.length / 2) || 1); i++) {
    let worker = cluster.fork();
    console.log(`子进程worker已创建: ${worker.process.pid} forked`);

    worker.send(`子进程发送消息:我是子进程${worker.process.pid}, 你好呀`);

    worker.on("message", (msg) => {
      console.log(
        `子进程接收消息:worker_${worker.process.pid} 接收到消息,内容是“ ${msg} ”`
      );
    });
  }
} else if (cluster.isWorker) {
  // 如果是子进程,执行具体任务。如开启新的http服务
  console.log(`fork worker 工作进程 ${process.pid} 已启动`);
  console.log(`这是工作进程 #${cluster.worker.id}`);
  process.on("message", (msg) => {
    console.log(`process onmessage msg is: ${msg}`);
    process.send(msg + ":叫爸爸");
  });

  // 工作进程(子进程)可以共享任何 TCP 连接。在本例子中,共享的是 HTTP 服务器。
  http
    .createServer((req, res) => {
      res.writeHead(200);
      res.end(`你好世界 #${JSON.stringify(cluster.worker, "\t", 2)} \n`);
    })
    .listen(3333);
}

// 主进程和工作进程的 listening 事件都会在工作进程调用 listen 方法的时候触发。
cluster.on("listening", (worker, address) => {
  console.log(`工作进程已连接到 ${address.address}:${address.port}`);
});

// 监听子进程断开事件
// 触发 disconnect 事情的原因有很多,可以是主动调用 worker.disconnect(),也可以是工作进程退出或者被 kill 掉。
cluster.on("disconnect", (worker) => {
  console.log(`工作进程 #${worker.id} 已断开连接`);
});

// exit 事件会在任何一个工作进程关闭的时候触发。
// 一般用来监测 cluster 中某一个进程是否异常退出,
// 如果退出的话使用 cluster.fork 创建新的进程,以保证有足够多的进程来处理请求。
//
// exitedAfterDisconnect 表示如果工作进程由于 .kill() 或 .disconnect() 而退出的话,值就是 true。
// exitedAfterDisconnect 表示如果是以其他方式退出的话,返回值就是 false。
// exitedAfterDisconnect 表示如果工作进程尚未退出,则为 undefined。
cluster.on("exit", (worker, code, signal) => {
  if (worker.exitedAfterDisconnect === true) {
    console.log(
      "工作进程 %d 关闭 (%s).这是自发退出,无需担心",
      worker.process.pid,
      signal || code
    );
  } else {
    console.log(
      "工作进程 %d 关闭 (%s).这是异常退出,重启中...",
      worker.process.pid,
      signal || code
    );
    cluster.fork();
  }
});

Cluster 开启多进程时候端口疑问讲解:

如果多个 Node 进程监听同一个端口时会出现 Error:listen EADDRIUNS 的错误,而 cluster 模块为什么可以让多个子进程监听同一个端口呢? 原因是 master 进程内部启动了一个 TCP 服务器,而真正监听端口的只有这个服务器,当来自前端的请求触发服务器的 connection 事件后,master 会将对应的 socket 具柄发送给子进程。

进程守护

PM2 管理

Docs

This is a .md using a custom component

Recommended IDE setup: VS Code + Volar

Vite Documentation | Vue 3 Documentation

Edit components/HelloWorld.vue to test hot module replacement.

More docs

...

Released under the MIT License.