NodeJS Cluster模块基础教程
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可以继续运行处理用户的请求。
在上面代码的基础上对masterProcess
和childProcess
进行修改:
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,敬请期待吧!
相关文章:

NodeJS Cluster模块基础教程
Cluster简介 默认情况下,Node.js不会利用所有的CPU,即使机器有多个CPU。一旦这个进程崩掉,那么整个 web 服务就崩掉了。 应用部署到多核服务器时,为了充分利用多核 CPU 资源一般启动多个 NodeJS 进程提供服务,这时就…...

[C++笔记]vector
vector vector的说明文档 vector是表示可变大小数组的序列容器(动态顺序表)。就像数组一样,vector也采用连续的存储空间来储存元素。这就意味着可以用下标对vector的元素进行访问,和数组一样高效。与数组不同的是,它的大小可以动态改变——…...
Python 迁移学习实用指南:1~5
原文:Hands-On Transfer Learning with Python 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 不要担心自己的形象,只关心如…...

【CSS重点知识】属性计算的过程
✍️ 作者简介: 前端新手学习中。 💂 作者主页: 作者主页查看更多前端教学 🎓 专栏分享:css重难点教学 Node.js教学 从头开始学习 ajax学习 标题什么是计算机属性确定声明值层叠冲突继承使用默认值总结什么是计算机属性 CSS属性值的计算…...

Java避免死锁的几个常见方法(有测试代码和分析过程)
目录 Java避免死锁的几个常见方法 死锁产生的条件 上死锁代码 然后 :jstack 14320 >> jstack.text Java避免死锁的几个常见方法 Java避免死锁的几个常见方法 避免一个线程同时获取多个锁。避免一个线程在锁内同时占用多个资源,尽量保证每个锁…...

go binary包
binary包使用与详解 最近在看一个第三方包的库源码,bigcache,发现其中用到了binary 里面的函数,所以准备研究一下。 可以看到binary 包位于encoding/binary,也就是表示这个包的作用是编辑码作用的,看到文档给出的解释…...

CompletableFuture使用详解(IT枫斗者)
CompletableFuture使用详解 简介 概述 CompletableFuture是对Future的扩展和增强。CompletableFuture实现了Future接口,并在此基础上进行了丰富的扩展,完美弥补了Future的局限性,同时CompletableFuture实现了对任务编排的能力。借助这项能力…...

4.15--设计模式之创建型之责任链模式(总复习版本)---脚踏实地,一步一个脚印
一、什么是责任链模式: 责任链模式属于行为型模式,是为请求创建了一个接收者对象的链,将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。 当一个请求从链式的首端发出时&a…...

STM32+W5500实现以太网通信
STM32系列32位微控制器基于Arm Cortex-M处理器,旨在为MCU用户提供新的开发自由度。它包括一系列产品,集高性能、实时功能、数字信号处理、低功耗/低电压操作、连接性等特性于一身,同时还保持了集成度高和易于开发的特点。本例采用STM32作为MC…...

全网最详细,Jmeter性能测试-性能基础详解,终成测试卷王(一)
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 发起请求 发起HTTP…...

人工智能概述
一、人工智能发展必备三要素 算法 数据 算力 CPU、GPU、TPU 计算力之CPU、GPU对比: CPU主要适合I\O密集型任务GPU主要适合计算密集型任务 什么样的程序适合在GPU上运行? 计算密集型的程序 所谓计算密集型(Compute-intensive)的程序,就是…...

API接口安全—webservice、Swagger、WEBpack
API接口安全—webservice、Swagger、WEBpack1. API接口介绍1.1. 常用的API接口类1.1.1. API接口分类1.1.1.1. 类库型API1.1.1.2. 操作系统型API1.1.1.3. 远程应用型API1.1.1.4. WEB应用型API1.1.1.5. 总结1.1.2. API接口类型1.1.2.1. HTTP类接口1.1.2.2. RPC类接口1.1.2.3. web…...
从前M个字母中取N个的无重复排列 [2*+]
目录 从前M个字母中取N个的无重复排列 [2*+] 程序设计 程序分析 从前M个字母中取N个的无重复排列 [2*+] 输出从前M个字母中取N个的无重复字母排列 Input 输入M N 1<=M=10, N<=M Output 按字典序输出排列 Sample Input 4 2 Sample Output A B A C A D B A B C B …...
ES forceMerge 强制段合并为什么会提升检索性能?
根据以前的测试,forceMerge段合并,将段的个数合并成一个。带来了将近一倍的性能提升,测试过程文档(请参考我的另外一篇文章):ES优化实战- forceMerge搜索提升测试报告_es forcemerge_水的精神的博客-CSDN博…...

macOS Ventura 13.3.1 (22E261) Boot ISO 原版可引导镜像
本站下载的 macOS 软件包,既可以拖拽到 Applications(应用程序)下直接安装,也可以制作启动 U 盘安装,或者在虚拟机中启动安装。另外也支持在 Windows 和 Linux 中创建可引导介质。 macOS Ventura 13.3.1 为 Mac 提供下…...

html+css+JavaScript+json+servlet的社区系统(手把手教学)
目录 课前导读: 一、系统前期准备 二、前端代码的编写 三、登陆页面简介 四、注册页面 五、社区列表页 六、社区详情页 七、社区发帖页 八、注销 九、访问链接 登陆页面http://175.178.20.77:8080/java106_blog_system/login.html 总结: 课前…...

UI Toolkit(1)
UI ToolkitUI Toolkit界面画布设置背景制作UI布局UI Toolkit界面 在Unity 2021LTS版本之后UI Toolkit也被内置在Unity中,Unity有意的想让UI Toolkit 成为UI的主要搭建方式,当然与UGUI相比还是有一定的差别。他们各有有点,这次我们就开始介绍…...

vLive带你走进虚拟直播世界
虚拟直播是什么? 虚拟直播是基于5G实时渲染技术,在绿幕环境下拍摄画面,通过实时抠像、渲染与合成,再推流到直播平台的一种直播技术。尽管这种技术早已被影视工业所采用,但在全民化进程中却是困难重重,面临…...

初谈 ChatGPT
引子 最近,小编发现互联网中的大 V 突然都在用 ChatGPT 做宣传:“ChatGPT不会淘汰你,能驾驭ChatGPT的人会淘汰你”、“带领一小部分人先驾驭ChatGPT”。 确实,ChatGPT这个新生事物,如今被视为蒸汽机、电脑、iPhone 般的…...
JAVA练习103-螺旋矩阵
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 前言 提示:这里可以添加本文要记录的大概内容: 4月9日练习内容 提示:以下是本篇文章正文内容,下面案例可供参考 一、题目-螺…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...