node如何实现多线程,nodejs 多进程
node是如何实现多进程的?如何部署节点项目?下面这篇文章带你掌握Node.js多流程模型和项目部署的知识,希望对你有帮助!
node.js速度课程简介:进入学习
昨天有朋友问快递项目怎么部署?所以我整理了这篇文章,主要是关于如何部署一个基于nodejs开发的服务器端程序,供有需要的朋友参考。
文章包含几个部分:
并线程化process node.js实现node.js环境的多进程服务器安装,用PM2管理Node.js项目,用Nginx
进程 VS 线程
进程
进程(process)是计算机操作系统分配和调度任务的基本单位实现接口服务的代理转发。打开任务管理器,可以看到电脑后台其实有很多程序在运行,每个程序都是一个进程。
现代浏览器基本都是多进程架构。以Chrome浏览器为例。打开“更多工具”-“任务管理器”,可以看到当前浏览器的进程信息。一个页面就是一个进程,除此之外,还有网络进程,GPU进程等等。
多进程架构可以确保应用程序更加稳定地运行。以浏览器为例。如果所有程序都在一个进程中运行,如果网络出现故障,或者页面呈现不正确,整个浏览器都会崩溃。通过多进程架构,即使网络进程崩溃,也不会影响已有页面的显示,最坏的情况是暂时无法访问网络。
线程
线程(thread)是操作系统能够进行运算调度的最小单位。它包含在流程中,是流程中的实际操作单元。比如一个程序就像一个公司,有几个部门,也就是几个流程;各部门的配合让公司正常运转,线头就是员工,具体的工人。
我们都知道JavaScript是单线程语言。这样设计是因为早期的JS主要用来写脚本程序,负责实现页面的交互效果。如果设计成多线程语言,首先是不需要,其次是多个线程共同操作一个dom节点,那么浏览器应该听谁的?当然,随着技术的发展,JS现在也支持多线程,但只是用来处理一些与dom操作无关的逻辑。
单进程存在的问题
单线程单进程带来一个严重的问题。一个正在运行的node.js程序,一旦主线程挂起,那么这个进程也挂起,整个应用程序也随之挂起。再者,现代计算机多是多核CPU,四核八线程,八核十六线程,都是很常见的设备。node.js作为单进程程序,浪费了多核CPU的性能。
针对这种情况,我们需要一个合适的多进程模型,将单进程node.js程序变成多进程架构。
Node.js 的多进程实现
Node.js实现多进程架构的常用方案有两种,都是使用原生模块,即child_process模块和cluster模块。
child_process
child_process是node.js的内置模块你从名字就能猜到它负责的是子进程相关的事情。
这个模块的具体用法我们就不细说了。其实它大概只有六七种方法,非常好理解。我们使用其中一个fork方法来演示如何实现多进程以及多进程之间的通信。
首先看一下准备好的演示案例的目录结构:
我们已经使用http模块创建了一个HTTP服务器。当一个/sum请求进来时,将通过child_process模块创建一个子进程,并将计算的逻辑通知子进程。同时,父进程还将监听子进程发送的消息:
//child_process.js
const http=require(http )
const { fork }=require( child _ process )
const server=http . create server((req,res)={
if (req.url==/sum) {
//fork方法接收一个模块路径,然后打开一个子进程运行子进程中的模块。
//childProcess表示创建的子流程。
let childProcess=fork(。/sum.js )
//向子进程发送消息
ChildProcess.send(子进程开始计算)
//监听父进程中子进程的消息
childProcess.on(message ,(data)={
res.end(数据)
})
//侦听子进程的关闭事件
childProcess.on(close ,()={
//如果子进程正常退出,出错挂机,就来这里。
Console.log(“子进程已关闭”)
childProcess.kill()
})
//侦听子进程的错误事件
childProcess.on(error ,()={
Console.log(“来自子进程的错误”)
childProcess.kill()
})
}
if (req.url==/hello) {
res.end(hello )
}
//模拟的父进程报告了一个错误。
if (req.url==/error) {
抛出新错误(“父进程中的错误”)
res.end(hello )
}
})
server.listen(3000,()={
console.log(“服务器在3000上运行”)
})sum.js用于模拟子进程要执行的任务。子进程监听父进程发送的消息,处理计算任务,然后将结果发送给父进程:
//sum.js
函数getSum() {
设总和=0
for(设I=0;我10000 * 1000 * 100;i ) {
总和=1
}
返回总和
}
//process是node.js中的全局对象,代表当前进程。在这种情况下,它是一个子流程。
//侦听来自主进程的消息
process.on(message ,(data)={
Console.log(主进程的消息:,数据)
const result=getSum()
//将计算结果发送给父进程
process.send(结果)
})打开终端,运行命令node 1.child_process:
访问浏览器:
接下来,模拟子进程报告错误的情况:
//sum.js
函数getSum() {
//.
}
//子进程运行5s后,模拟进程挂起。
setTimeout(()={
抛出新错误(“错误”)
}, 1000 * 5)
process.on(message ,(data)={
//.
})再次访问浏览器,5秒钟后观察控制台:
子进程已经挂起,再访问另一个url :/hello,
可以看到父进程仍然可以正确处理请求,表示子进程报错,并不会影响父进程的运行。
接下来我们模拟一下父进程报错的场景,注释掉sum.js模块模拟的错误,然后重启服务。浏览器访问/错误:
发现父进程挂起后,整个node.js程序自动退出,服务完全崩溃。没有挽回的余地。
可见通过child_process的fork方法实现node.js的多进程架构并不复杂。进程之间的通信主要通过send和on方法进行。从这个命名可以知道,它的底层应该是一个发布-订阅模式。
但是它有一个严重的问题。虽然子进程不影响父进程,但是它是一旦父进程出错挂掉,所有的子进程会被”一锅端掉“。因此,本方案适用于将一些复杂耗时的运算,fork 出一个单独的子进程去做。更准确地说,这种用法是用来代替多线程的实现,而不是多进程。
cluster
使用child_process模块实现多进程,好像不是很有用。因此,一般建议使用集群模块来实现node.js的多进程模型
集群,集群的意思,相信大家都很熟悉这个名词。比如以前公司只有一个前台,有时候忙到不能及时接待访客。现在公司分配了四个前台,即使三个忙,还有一个可以接待新访客。这大概就是集群的意思。对于同一件事,分配给不同的人去做是合理的,这样才能保证做到最好。
集群模块使用起来也相对简单。如果当前进程是主进程,则根据 CPU 的核数创建合适数量的子进程,同时监听子进程的 exit 事件,有子进程退出,就重新 fork 新的子进程。如果不是子进程,则进行实际业务的处理。
const http=require(http )
const cluster=require(cluster )
const CPU=require( OS )。CPU()
if (cluster.isMaster) {
//程序启动时,先去这里,根据CPU核的数量,创建多个子进程。
for(设I=0;i cpus.lengthi ) {
//创建子进程
cluster.fork()
}
//当任何子进程挂起时,集群模块都会发出‘exit’事件。此时,通过再次调用fork来重新启动该过程。
cluster.on(exit ,()={
cluster.fork()
})
}否则{
//fork方法执行创建子进程,同时会再次执行模块。这时候逻辑就到此为止了。
const server=http . create server((req,res)={
console.log(process.pid)
res.end(确定)
})
server.listen(3000,()={
console.log(服务器正在3000上运行, pid: process.pid )
})
}启动服务:
正如您所看到的,集群模块创建了许多子流程,就好像每个子流程都在运行相同的web服务。
注意,此时并非是这些子进程共同监听同一个端口。由createServer方法创建的服务器仍然负责端口监控,请求被转发给每个子进程。
让我们写一个请求脚本来请求上面的服务,看看效果。
//request.js
const http=require(http )
for(设I=0;i 1000i ) {
http.get(http://localhost:3000 )
}使用node命令执行文件,然后查看原始控制台:
打印出专门处理请求的不同子流程的流程id。
这就是集群模块实现的nodd.js的多进程架构。
当然,我们在部署node.js项目的时候,不会这么干写和使用集群模块。有一个非常有用的工具叫PM2,是基于集群模块的流程管理工具。它的基本用法将在后面的章节中介绍。
小结
到目前为止,我们已经花了一些时间介绍node.js中多进程的知识其实我们只想说明为什么需要使用 pm2 来管理 node.js 应用。由于篇幅有限,描述不准确/详细,本文仅作简要介绍。如果是第一次接触这一块内容的朋友,可能不太清楚,也无所谓。后面还会有更详细的文章。
部署实践
准备一个 express 项目
本文准备了一个使用express开发的示例程序,请单击此处访问。
它主要实现一个接口服务。访问/api/users时,用mockjs模拟10条用户数据,返回一个用户列表。同时,将启动一个计时器来模拟错误情况:
const express=require(express )
const Mock=require(mockjs )
const app=express()
app.get(/api/users ,(req,res)={
const userList=Mock.mock({
用户列表10: [{
id 1: 1,
名称: @cname ,
电子邮件: @email
}]
})
setTimeout(()={
抛出新错误(“服务器故障”)
}, 5000)
资源状况(200)
res.json(用户列表)
})
app.listen(3000,()={
Console.log(服务启动:3000 )
})在本地测试它,并在终端中执行命令:
Node server.js打开浏览器,进入用户列表界面:
五秒钟后,服务器将挂断:
我们用pm2来管理应用之后,就可以解决这个问题了。
讨论:express 项目是否需要打包
通常一个vue/react项目完成后,我们会先打包,然后发布。其实前端项目是需要打包的,主要是因为程序的最终运行环境是浏览器,浏览器存在各种兼容性问题和性能问题,比如:
不支持高级语法,所以ES6需要编译成。vue,jsx,ES5语法无法识别的ts文件。需要对其进行编译,以减少代码量,节省带宽资源,提高资源加载速度.但是用express.js或者koa.js开发的项目就没有这些问题。还有,Node.js 采用 CommonJS 模块化规范,有缓存的机制;同时,只有当模块在被用到时,才会被导入。如果你把它打包成一个文件,实际上会浪费这个优势。所以对于node.js项目来说,不需要打包。
服务器安装 Node.js
本文以CentOS系统为例进行演示。
NVM
为了方便切换节点的版本,我们使用nvm来管理节点。
Nvm(Node Version Manager)是Node.js的版本管理工具通过它,Node可以在多个版本之间任意切换,避免了需要切换版本时的重复下载安装操作。
Nvm的官方仓库是github.com/nvm-sh/nvm.因为它的安装脚本存储在githubusercontent网站上,所以经常无法访问。所以我在gitee上新建了一个镜像仓库,这样我就可以向gitee索要它的安装脚本了。
用curl命令下载安装脚本,用bash执行脚本,会自动完成nvm的安装:
# curl-o-https://gitee.com/hsyq/nvm/raw/master/install.sh bash安装完成后,我们将打开一个新窗口来使用nvm:
[root@ecs-221238 ~]# nvm
-v0.39.1可以正常打印版本号,说明nvm已经安装成功。
安装 Node.js
现在您可以使用nvm来安装和管理节点。
检查可用的节点版本:
# nvm ls-远程安装节点:
# nvm安装18.0.0检查安装的节点版本:
[root@ecs-221238 ~]# nvm列表
- v18.0.0
默认值- 18.0.0 (- v18.0.0)
iojs - N/A(默认)
不稳定-不适用(默认)
节点稳定(- v18.0.0)(默认)
稳定-18.0 (- v18.0.0)(默认)选择要使用的版本:
# nvm使用18.0.0 npm将在安装节点时自动安装。检查节点和npm的版本号:
[root@ecs-221238 ~]# node -v
v18.0.0
[root@ecs-221238 ~]# npm -v
8.6.0默认的npm映像源是官方地址:
[root@ecs-221238 ~]# npm配置获取注册表
https://registry.npmjs.org/切换到国内淘宝的镜像源;
[root @ ECS-221238 ~] # npmconfig设置注册表https://registry.npmmirror.com到目前为止,服务器已经安装了节点环境,配置了npm。
项目上传到服务器
方法很多,要么从Github/GitLab/Gitee仓库下载到服务器,要么通过ftp工具上传到本地。步骤很简单,就不再演示了。
演示项目位于/www目录中:
服务器开放端口
一般云服务器只开放22个端口进行远程登录。80、443等常用端口不开放。此外,我们准备好的express项目在端口3000上运行。所以你需要先去云服务器的控制台,找到安全组,添加几个规则,打开端口80和3000。
使用 PM2 管理应用
在开发阶段,我们可以使用nodemon做实时监控和自动重启,提高开发效率。在生产环境中,就要牺牲掉大杀手锏——PM2。
基本使用
首先全球安装pm2:
# npm i -g pm2执行pm2 -v命令以查看安装是否成功:
[root@ecs-221238 ~]# pm2
-v5.2.0切换到项目目录,首先安装依赖项:
CD/www/express-演示
然后,Ninstall使用pm2命令启动应用程序。
pm2启动app.js -i max
//或者
pm2启动server.js -i 2
此时,应用程序启动。PM2将以守护程序的形式管理应用程序。该表显示了关于应用程序运行的一些信息,如运行状态、CPU利用率、内存利用率等。
在本地浏览器中访问界面:
集群模式是多进程多实例模式,当请求进来时,它将被分配给其中一个进程。就像我们之前看到的集群模块的用法一样,由于pm2的守护,即使一个进程挂起,也会立即重启。
返回到服务器终端,执行pm2 logs命令来检查pm2的日志:
可以看到,id为1的应用实例挂起,pm2会立即重启实例。注意,这里的id是应用程序实例的id,而不是进程id。
至此,一个express项目的简单部署已经完成。通过使用pm2工具,我们可以基本保证我们项目的稳定可靠运行。
PM2 常用命令小结
以下是pm2工具常用的一些命令,供参考。
#分叉模式
2 pmstartapp.js-nameapp #将应用程序的名称设置为app。
#集群模式
#使用负载平衡启动4个进程
pm2启动app.js -i 4
# 4进程将使用负载平衡启动,具体取决于可用的CPU
pm2启动app.js -i 0
#相当于上述命令的功能
pm2启动app.js -i max
#通过3个额外流程扩展应用程序
pm2秤app 3
#将应用程序扩展或缩减为2个流程
pm2秤app 2
#检查申请状态
#显示所有进程的状态
pm2列表
#以原始JSON格式打印所有进程的列表
pm2 jlist
#用美化的JSON打印所有进程的列表
pm2漂亮列表
#显示特定过程的所有信息
pm2描述0
#使用仪表板监控所有流程
pm2监测
#日志管理
#实时显示所有应用程序的日志
pm2日志
#实时显示应用程序的日志
pm2日志应用程序
#使用json格式实时显示日志,只输出新生成的日志,不输出旧的。
pm2日志- json
#应用程序管理
#停止所有进程
pm2全部停止
#重新启动所有进程
pm2全部重启
#停止具有指定id的进程
pm2停止0
#使用指定的id重新启动进程
pm2重启0
#删除id为0的进程
pm2删除0
#删除所有进程
PMDELETE ALL每个命令都可以自己试一下看效果。
下面是monit命令,它可以在终端中启动一个面板,实时显示应用程序的运行状态。pm2管理的所有应用程序都可以通过上下箭头进行切换:
进阶:使用 pm2 配置文件
PM2非常强大,远远超过以上命令。在实际的项目部署中,可能需要配置日志文件、观察模式、环境变量等等。每次手动输入命令非常繁琐,所以pm2提供了配置文件来管理和部署应用。
可以通过以下命令生成配置文件:
[root @ ECS-221238 express-demo]# PM2初始化简单
file/www/express-demo/ecosystem.config.js generated将生成一个ecosystem . config . js文件:
模块.导出={
应用程序:[{
名称: app1 ,
脚本:。/app.js
}]
}也可以自己创建一个配置文件,比如app.config.js:
const path=require(path )
模块.导出={
//一个配置文件可以同时管理多个node.js应用程序
//apps是一个数组,每一项都是一个应用的配置。
应用程序:[{
//应用程序名称
名称:快速演示,
//应用门户文件
脚本:。/server.js ,
//启动应用有两种模式:集群和fork,默认为fork。
exec_mode:集群,
//创建的应用程序实例的数量
实例:“最大值”,
//打开监控,当文件改变时自动重启应用程序。
手表:真的,
//忽略一些目录文件更改。
//因为日志目录放在项目路径下,所以必须忽略,否则应用程序会开始生成日志,pm2监测到变化会重启,重启会生成日志,进入无限循环。
ignore_watch: [
节点模块,
“日志”
],
//错误日志存储路径
err _ file:path . resolve(_ _ dirname, logs/error.log ),
//打印日志存储路径
out _ file:path . resolve(_ _ dirname, logs/out.log ),
//设置日志文件中每个日志前面的日期格式
log_date_format:年-月-日时:分:秒,
}]
}让pm2使用配置文件管理节点应用程序:
PMStartApp.config.js现在pm2管理的应用会把日志放在项目目录下(默认是放在pm2的安装目录下),可以监控文件的变化,自动重启服务。
更多有用的配置,请参考PM2官方文件,点击这里访问。
:上面
Nginx 代理转发接口
,我们直接曝光了nodejs项目的3000端口。一般我们用nginx做代理转发,对外只暴露80端口。
安装 Nginx
首先需要在服务器中安装nginx。有三种方式:
下载源代码,用docker编译安装,用包管理工具安装。我这里的系统是CentOS 8,可用的yum源码已经更换,可以直接安装nginx。如果您的操作系统是CentOS 7或其他发行版,您可以搜索合适的安装方法。
使用yum安装:
# yum install -y nginx并启动nginx:
# systemctl启动nginx打开浏览器访问服务器地址,可以看到nginx的默认主页:
配置接口转发
为项目创建新的配置文件:
# vim/etc/nginx/conf . d/express . conf监听端口80,并将所有请求转发给服务器本地端口3000的程序:
服务器{
听80;
server _ name iron fan . site;
位置/{
proxy _ pass http://localhost:3000;
}
} } conf目录下的配置文件,将由主配置文件/etc/nginx/nginx.conf加载:
修改配置文件后,请确保重新启动服务:
# systemctl重启nginx然后在本地打开浏览器,去掉原来的端口号3000,直接访问完整的url:
至此,接口转发的配置完成。从用户的角度来看,这也叫反向代理。
总结
首先我们系统的解释了为什么要在node.js项目中启动多进程,以及实现的两种方式:
child_process模块的fork方法,在cluster模块的fork方法之后,讲解了如何在Linux服务器中安装node环境以及部署node.js项目的一般流程,并重点讲解了pm2:
将项目上传到服务器
安装项目依赖项
使用pm2管理应用
最后说明了如何使用nginx实现接口的代理转发,将用户请求转发到本地3000端口服务。
到目前为止,我们已经完成了本文的目标,将一个 express 项目部署到服务器,并能稳定可靠的运行。
在下一篇文章中,我们将使用Github Actions来实现CI/CD,这将使项目的部署更加方便和高效。
本文演示代码已上传至Github,点击即可访问。
更多关于node的信息,请访问:nodejs教程!以上是node如何实现多流程的详细说明。如何部署节点项目?更多详情请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。