NodeJS Cluster模块基础教程

news2025/1/31 8:24:27

Cluster简介

默认情况下,Node.js不会利用所有的CPU,即使机器有多个CPU。一旦这个进程崩掉,那么整个 web 服务就崩掉了。

应用部署到多核服务器时,为了充分利用多核 CPU 资源一般启动多个 NodeJS 进程提供服务,这时就会使用到 NodeJS 内置的 Cluster 模块了。Cluster模块可以创建同时运行的子进程(Worker进程),同时共享同一个端口。每个子进程都有自己的事件循环内存V8实例

NodeJS Cluster是基于Master-Worker模型的,Master负责监控Worker的状态并分配工作任务,Worker则负责执行具体的任务。Master和Worker之间通过IPC(进程间通信)传递消息,进程之间没有共享内存

主进程也做叫Master进程,子进程也叫做Worker进程,下面会混用这两种叫法

HTTP服务器和Cluster

使用NodeJS构建http服务器非常简单,代码如下:

//app.js
const http = require("http");
const pid = process.pid;
http
  .createServer((req, res) => {
    for (let i = 1e7; i > 0; i--) {}
    console.log(`Handling request from ${pid}`);
    res.end(`Hello from ${pid}\n`);
  })
  .listen(8081, () => {
    console.log(`Started ${pid}`);
  });
****

为了模拟一些实际的CPU工作,我们执行了1000万次空循环,启动服务之后,可以使用浏览
器或curl向http://localhost:8080发送请求

curl localhost:8081

返回如下:

Hello from 33720

使用autocannon压测服务器

安装autocannon:

npm i -g autocannon

使用autocannon:

autocannon -c 200 -d 10 http://localhost:8081

上面的命令将在10秒内为服务器发起200个并发连接

运行结果如下:

┌─────────┬────────┬────────┬─────────┬─────────┬───────────┬───────────┬─────────┐
│ Stat    │ 2.5%   │ 50%    │ 97.5%   │ 99%     │ Avg       │ Stdev     │ Max     │
├─────────┼────────┼────────┼─────────┼─────────┼───────────┼───────────┼─────────┤
│ Latency │ 453 ms │ 651 ms │ 1003 ms │ 1829 ms │ 750.95 ms │ 208.08 ms │ 1968 ms │
└─────────┴────────┴────────┴─────────┴─────────┴───────────┴───────────┴─────────┘

3k requests in 10.02s

Latency表示延迟,可以看到平均延迟是750ms,最慢的响应延迟接近2S,在10S服务器一共接受了3000请求

使用Cluster模块进行扩展

const cluster = require("node:cluster");
const http = require("node:http");
const numCPUs = require("node:os").cpus().length;
const process = require("node:process");
if (cluster.isMaster) {
  //处理主进程逻辑
  masterProcess();
} else {
  //处理子进程逻辑
  childProcess();
}
function masterProcess() {
  console.log(`Master ${process.pid} is running`);

  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
}
function childProcess() {
  http
    .createServer((req, res) => {
      for (let i = 1e7; i > 0; i--) {}
      console.log(`Handling request from ${pid}`);
      res.end(`Hello from ${pid}\n`);
    })
    .listen(8081, () => {
      console.log(`Started ${pid}`);
    });
}

将代码保存在 app.js 文件中并运行执行: $node app.js 。输出类似于下面这样:

Master 33931 is running
Worker Started 33932
Worker Started 33935
Worker Started 33936
Worker Started 33934
Worker Started 33939
Worker Started 33933
Worker Started 33937
Worker Started 33938

通过 isMaster 属性,可以判断是否为 Master 进程,Master进程中执行 cluster.fork()创建与CPU核心数相同的子进程。

fork() 是创建一个新的NodeJS进程,就像通过命令行使用 $node app.js 运行一样,会有很多进程运行 app.js 程序。

子进程创建和执行时,和master一样,导入cluster模块,执行 if 语句。但子进程的 cluster.isMaster的值为 false

fork的过程如下:

在这里插入图片描述

压测Cluster

autocannon -c 200 -d 10 [http://localhost:8081](http://localhost:8081/)

输出结果如下:

┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬────────┐
│ Stat    │ 2.5%   │ 50%    │ 97.5%  │ 99%    │ Avg       │ Stdev    │ Max    │
├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼────────┤
│ Latency │ 109 ms │ 136 ms │ 260 ms │ 282 ms │ 142.75 ms │ 32.09 ms │ 397 ms │
└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴────────┘

14k requests in 10.02s

可以看到平均延迟为142ms,最大的延迟为397ms,服务器在10S内一共处理了14000个请求

使用Cluster之后性能提升大约4倍( 14000次/10s 对比 3000次/10s

测试Cluster模块的可用性

为了测试服务的可用性,我们会在子进程中使用setTimeout抛出一些错误

masterProcess方法和childProcess进行修改:

function masterProcess() {
  //...
  //监听子进程的退出事件
  cluster.on("exit", (worker, code) => {
    //子进程异常退出
    if (code !== 0 && !worker.exitedAfterDisconnect) {
      console.log(
        `Worker ${worker.process.pid} crashed. ` + "Starting a new worker"
      );
      cluster.fork();
    }
  });
}
function childProcess() {
  // 随机的1到3秒内等待一段时间,然后抛出一个名为"Ooops"的错误
  setTimeout(() => {
    throw new Error("Ooops");
  }, Math.ceil(Math.random() * 3) * 1000);
  //...
}

在这段代码中,一旦主进程接收到“退出”事件。我们检查code状态码和
worker.exitedAfterDisconnect标记,来判断进程是否为异常退出,然后启动一个新的Worker进程。当终止的Worker进程重新启动时,其他工作进程仍然可以服务请求,从而不会影响应用程序的可用性。

code是用于检查进程的退出码,code为 0,则表示正常退出,如果不是,则表示进程非正常退出。

worker.exitedAfterDisconnect是NodeJS中cluster模块Worker对象的一个属性,用于指示工作进程是否在主进程调用其disconnect()方法后退出。

如果Worker进程成功地完成了disconnect过程并正常退出,则worker.exitedAfterDisconnect将被设置为true。否则,该属性将保持为false,表示该进程已经以其他方式退出。

使用autocannon进行压测autocannon -c 200 -d 10 [http://localhost:8081](http://localhost:8081/)

结果如下:

┌─────────┬────────┬────────┬─────────┬─────────┬───────────┬───────────┬─────────┐
│ Stat    │ 2.5%   │ 50%    │ 97.5%   │ 99%     │ Avg       │ Stdev     │ Max     │
├─────────┼────────┼────────┼─────────┼─────────┼───────────┼───────────┼─────────┤
│ Latency │ 101 ms │ 398 ms │ 1301 ms │ 1498 ms │ 466.99 ms │ 301.07 ms │ 2405 ms │
└─────────┴────────┴────────┴─────────┴─────────┴───────────┴───────────┴─────────┘

14k requests in 10.03s
1k errors (0 timeouts)

14000个请求中,有1000个出现错误,服务的可用性大约为92%,对于一个经常崩溃的应用程序来说,它的可用性也不差

主进程和子进程通信

稍微更新一下之前的代码,就能允许Master进程向Worker进程发送和接收消息,Wordker进程也可以从Master进程接收和发送消息:

function childProcess() {
  console.log(`Worker ${process.pid} started`);
  //监听主进程的消息
  process.on("message", function (message) {
    console.log(
      `Worker ${process.pid} recevies message '${JSON.stringify(message)}'`
    );
  });
  console.log(`Worker ${process.pid} sends message to master...`);
  //给主进程发消息
  process.send({ msg: `Message from worker ${process.pid}` });
}

在子进程中,使用 process.on('message', handler)方法注册一个监听器,当主进程给这个子进程发送消息的时候,会执行handler回调,然后使用 process.send()向主进程发送消息

function masterProcess() {
  console.log(`Master ${process.pid} is running`);
  let workers = [];
  // fork 子进程
  for (let i = 0; i < numCPUs; i++) {
    const worker = cluster.fork();
    workers.push(worker);

    // 监听子进程的消息
    worker.on("message", function (message) {
      console.log(
        `Master ${process.pid} recevies message '${JSON.stringify(
          message
        )}' from worker ${worker.process.pid}`
      );
    });
  }

  // 给每个子进程发送消息
  workers.forEach(function (worker) {
    console.log(
      `Master ${process.pid} sends message to worker ${worker.process.pid}...`
    );
    worker.send({ msg: `Message from master ${process.pid}` });
  }, this);

}

我们先监听子进程的message事件,最后在Master进程给每个 Worker进程发送消息

输出会类似于下面这样:

Master 88498 is running
Master 88498 sends message to worker 88500...
Master 88498 sends message to worker 88501...
Worker 88501 started
Worker 88501 sends message to master...
Master 88498 recevies message '{"msg":"Message from worker 88501"}' from worker 88501
Worker 88501 recevies message '{"msg":"Message from master 88498"}'
Worker 88500 started
Worker 88500 sends message to master...
Master 88498 recevies message '{"msg":"Message from worker 88500"}' from worker 88500
Worker 88500 recevies message '{"msg":"Message from master 88498"}'

使用Cluster进行优雅的重启

当我们更新代码的时候,可能需要重新启动NodeJS。重新启动应用程序时,会出现一个小的空窗期:在我们重启单进程的NodeJS过程中,服务器会无法处理用户的请求

使用Cluster可以解决这个问题,具体做法如下:一次重新启动一个Worker,剩下的Worker可以继续运行处理用户的请求。

在上面代码的基础上对masterProcesschildProcess进行修改:

function masterProcess() {
  console.log(`Master ${process.pid} is running`);
  let workers = [];
  // fork 子进程
  for (let i = 0; i < numCPUs; i++) {
    const worker = cluster.fork();
    workers.push(worker);
  }
  process.on("SIGUSR2", async () => {
    restartWorker(0);
    function restartWorker(i) {
      if (i >= workers.length) return;
      const worker = workers[i];
      console.log(`Stopping worker: ${worker.process.pid}`);

      worker.disconnect(); 
			//监听子进程的退出事件
      worker.on("exit", () => {
				//判断子进程是否完成disconnect过程并正常退出
        if (!worker.exitedAfterDisconnect) return;
        const newWorker = cluster.fork(); //[4]
        newWorker.on("listening", () => {
          //当新的子进程开始监听端口
          //重启下一个子进程
          restartWorker(i + 1);
        });
      });
    }
  });
}
function childProcess() {
  http.createServer((req, res) => {
      console.log("Worker :>> ", `Worker ${process.pid}`);
      res.writeHead(200);
      res.end("hello world\n");
    })
    .listen(8000);
  console.log(`Worker ${process.pid} started`);
}

masterProcess方法新增了process.on("SIGUSR2", callback), SIGUSR2是一种信号,通常用于向一个进程发送自定义的指令,比如要求应用程序执行某些操作(如重启、重新加载配置文件等)。

当主进程接收到SIGUSR2信号时,它会遍历所有Workder进程并调用disconnect方法,然后监听子进程的退出事件。

Workder进程退出之后,Master进程就会重新创建新的Workder进程,并等待其开始监听端口。然后重启下一个Workder进程。

childProcess 方法则是启动了一个Http服务器。

通过这种方式,整个应用程序可以在不中断服务的情况下进行平滑重启,从而实现无缝升级和维护。

在生产环境中,我们一般会使用PM2来进行进程管理,,PM2基于cluster,提供负载均衡、过程监控、零停机重启和其他功能。

总结

本文介绍了使用NodeJS Cluster模块进行多进程处理的方法,包括如何创建子进程、压测Cluster、主进程和子进程通信、以及如何使用Cluster进行优雅的重启。

在生产环境中,我们一般会使用PM2来进行进程管理,PM2基于cluster,提供负载均衡、过程监控、零停机重启和其他功能。下篇文章会介绍一下PM2,敬请期待吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/413393.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【java】面向对象的编程基础

文章目录面向对象的编程基础定义方法重载执行顺序静态变量和方法加载顺序包和访问控制类的封装object类方法重写抽象类接口枚举类面向对象的编程基础 定义 public class person { String name; int age; char sex; person(String name,int age,char sex) {this.ageage;this.…

【华为OD机试】1043 - 从单向链表中删除指定值的节点

文章目录一、题目&#x1f538;题目描述&#x1f538;输入输出&#x1f538;样例1&#x1f538;样例2二、代码参考作者&#xff1a;KJ.JK&#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &am…

计算机网络笔记(横向)

该笔记也是我考研期间做的整理。一般网上的笔记是按照章节纪录的&#xff0c;我是按照知识点分类纪录的&#xff0c;大纲如下&#xff1a; 文章目录1. 各报文1.1 各报文头部详解1.2 相关口诀2. 各协议2.1 各应用层协议使用的传输层协议与端口2.2 各协议的过程2.2.1 数据链路层的…

零死角玩转stm32中级篇2-IIC总线

本篇博文目录:一.IIC基础知识1.什么是IIC总线2.IIC总线和串口有什么区别3.IIC总线是怎么实现多机通信4.仲裁是什么5.如果当前有一个从机进行了IIC通信又来了一个优先级高的从机&#xff0c;这时会打断前一个通信吗?6.IIC是怎么保证地址的唯一性7.在IIC总线协议中&#xff0c;规…

走进小程序【六】微信小程序架构之【视图层】万字详解

文章目录&#x1f31f;前言&#x1f31f;小程序架构&#x1f31f;视图层 View&#x1f31f;WXML&#x1f31f;数据绑定&#x1f31f;列表渲染&#x1f31f;条件渲染&#x1f31f;模板&#x1f31f;WXSS&#x1f31f;尺寸单位&#x1f31f;样式导入&#x1f31f;内联样式&#x…

VIM 编辑器使用教程

我们如果要在终端模式下进行文本编辑或者修改文件就可以使用 VI/VIM 编辑器&#xff0c;Ubuntu 自带了 VI 编辑器&#xff0c;但是 VI 编辑器对于习惯了 Windows 下进行开发的人来说不方便&#xff0c;比如竟然 不能使用键盘上的上下左右键调整光标位置。因此我推荐大家使用 V…

PADS-按键、蜂鸣器、继电器PCB封装设计

1 按键PCB封装设计 1.1 查看元件手册, 得知焊盘尺寸&#xff0c;同时需要观察按键&#xff0c;用丝印来进行表示。 1.2 进入PADS-Layout 无模命令UMM G0.254 GD0.254进行设计 放置一个表贴端点&#xff0c;更改矩形尺寸&#xff0c;同时计算与原点的距离&#xff0c;这里我们按…

流量整形(GTS和LR)

Generic Traffic Shaping通用流量整形 通用流量整形(简称GTS)可以对不规则或不符合预定流量特性的流量进行整形,以保证网络上下游之间的带宽匹配,避免拥塞发生。 GTS与CAR一样,都采用了令牌桶技术来控制流量。GTS与CAR的主要区别在于:利用CAR进行报文流量控制时,…

DFIG控制7:不平衡电网下的转子侧控制

DFIG控制7&#xff1a;不平衡电网下的转子侧控制。主要是加入了转子侧的电流负序分量控制器。 本文基于教程的第7部分&#xff1a; DFIM Tutorial 7 - Asymmetrical Voltage Dips Analysis in DFIG based Wind Turbines 控制策略简介 来自&#xff1a; H. Abu-Rub, M. Malin…

【Java实现】约瑟夫问题的Java代码实现

约瑟夫问题&#xff08;Josephus Problem&#xff09;是一个经典的数学问题&#xff0c;描述了一群人围成一圈报数&#xff0c;每报到第几个人就会被杀死&#xff0c;直到最后只剩下一个人。 1&#xff09;设编号为 1&#xff0c;2&#xff0c;3 ... n 的 n 个人围坐一圈。 2&…

MySQL之数据类型

目录 一 数值类型 1 int类型&#xff0c;以tinyint为例 范围&#xff1a; 越界问题&#xff1a; 验证&#xff1a; 2 bit&#xff08;位&#xff09; 应用&#xff1a; 显示问题&#xff1a; 3 小数 1 float[(M,D)] [unsigned] 范围&#xff1a; 2 decimal 精度问…

TCP分包和粘包

文章目录TCP分包和粘包TCP分包TCP 粘包分包和粘包解决方案&#xff1a;TCP分包和粘包 TCP分包 场景&#xff1a;发送方发送字符串”helloworld”&#xff0c;接收方却分别接收到了两个数据包&#xff1a;字符串”hello”和”world”发送端发送了数量较多的数据&#xff0c;接…

神策数据荣获“MarTech 领域最具商业合作价值企业”称号

近日&#xff0c;数据猿 2023 年度 3 月“企业盘点”活动落下帷幕&#xff0c;《2023 中国 MarTech 领域最具商业合作价值企业盘点》正式对外发布。神策数据依托在 MarTech 领域的专业度与知名度&#xff0c;被评为“MarTech 领域最具商业合作价值企业”。本次盘点活动从企业相…

pytorch 线性回归总结

测试1(y3∗x1−4∗x2y3*x_{1}-4*x_{2}y3∗x1​−4∗x2​),lr1e-2 %matplotlib inline import torch import numpy as np torch.manual_seed(1) from torch.nn import Linear from torch.autograd import Variable import torch.nn as nn import random np.random.seed(1) rand…

代码随想录-68-669. 修剪二叉搜索树

目录前言题目1.按照二叉搜索树特性遍历整棵二叉搜索树&#xff0c;2. 本题思路分析&#xff1a;3. 算法实现4. 算法坑点前言 我在刷卡哥的“代码随想录”&#xff0c;自己的总结笔记均会放在“算法刷题-代码随想录”该专栏下。 代码随想录此题链接 题目 1.按照二叉搜索树特性…

JavaWeb开发 —— JavaScript(JS)

目录 一、什么是JavaScript &#xff1f; 二、引入方式 三、基础语法 1. 书写语法 2. 输出语句 3. 变量 4. 数据类型 5. 运算符 6. 类型转换 四、函数 五、对象 1. Array数组 2. String 字符串 3. JSON 4. BOM 5. DOM 六、时间监听 一、什么是JavaSc…

MAE论文笔记+Pytroch实现

Masked Autoencoders Are Scalable Vision Learners&#xff0c; 2021 近期在梳理Transformer在CV领域的相关论文&#xff0c;落脚点在于如何去使用Pytroch实现如ViT和MAE等。通过阅读源码&#xff0c;发现不少论文的源码都直接调用timm来实现ViT。故在此需要简单介绍一下timm…

Vulnhub_Pylington

目录 一、信息收集 &#xff08;一&#xff09;端口服务探测 &#xff08;二&#xff09;目录扫描 二、漏洞挖掘 &#xff08;一&#xff09;robots敏感信息泄露 &#xff08;二&#xff09;python IDE沙箱绕过RCE 1. python敏感函数沙盒绕过 2. exec(__import_…

2.3 连续性随机变量

思维导图&#xff1a; 学习目标&#xff1a; 我会按照以下步骤学习连续型随机变量&#xff1a; 复习概率论的基础知识&#xff0c;包括概率、期望、方差等概念和公式&#xff0c;以及离散型随机变量的概率分布函数和概率质量函数的概念和性质。 学习连续型随机变量的概念和性…

在线文章生成-自动文章生成

文章生成软件 文章生成软件是一种能够自动生成文章的创作工具&#xff0c;可以大幅提高创作效率并节省创作成本。使用文章生成软件可以有效缩短文章的创作时间&#xff0c;尤其是在大批量的文章生产和时间紧迫的情况下&#xff0c;有着重要的作用。以下是文章生成软件可以为我…