PM2 负载均衡

负载均衡的一般概念

负载均衡是高可用网络基础架构的关键组件,通常用于将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性能和可靠性。

一个没有负载均衡的 web 架构类似下面这样:

在这里用户是直连到 web 服务器,如果这个服务器宕机了,那么用户自然也就没办法访问了。另外,如果同时有很多用户试图访问服务器,超过了其能处理的极限,就会出现加载速度缓慢或根本无法连接的情况。


而通过在后端引入一个负载均衡器和至少一个额外的 web 服务器,可以缓解这个故障。通常情况下,所有的后端服务器会保证提供相同的内容,以便用户无论哪个服务器响应,都能收到一致的内容。


从图里可以看到,用户访问负载均衡器,再由负载均衡器将请求转发给后端服务器。在这种情况下,单点故障现在转移到负载均衡器上了。这里又可以通过引入第二个负载均衡器来缓解,但在讨论之前,我们先探讨下负载均衡器的工作方式。



PM2 的负载均衡


Node.js 的单线程架构

NodeJS是基于chrome浏览器的V8引擎构建的,也就说明它的模型与浏览器是类似的。我们的javascript会运行在单个进程的单个线程上。这样有一个好处:

状态单一

没有锁

不需要线程间同步

减少系统上下文的切换

有效提高单核CPU的使用率


但是V8引擎的单进程单线程并不是完美的结构,现如今CPU基本上都是多核的。真正的服务器往往有好几个CPU(像我们的线上物理机有12个核),所以,这就将抛出NodeJS实际应用中的第一个问题:“如何充分利用多核CPU服务器?”


另外,由于Node执行在单线程上,一旦单线程出现未捕获的异常,就会造成这个进程crash。所以就遇到了第二个问题:“如何保证进程的健壮性和稳定性?”


从严格意义上来讲,Node其实并不是真正的单线程架构,因为Node自身还有I/O线程存在(网络I/O、磁盘I/O),这些I/O线程是由更底层的libuv处理,这部分线程对于JavaScript开发者来说是透明的。JavaScript代码永远运行在V8上,是单线程的。所以表面上来看NodeJS是单线程的。


Node.js 的多进程架构

面对单进程单线程对多核使用率不高的问题,按照之前的经验,每个进程各使用一个CPU即可,以此实现多核CPU的利用。Node提供了child_process模块,并且也提供了fork()方法来实现进程的复制(只要是进程复制,都需要一定的资源和时间。Node复制进程需要不小于10M的内存和不小于30ms的时间)。


这样的解决方案就是*nix系统上最经典的Master-Worker模式,又称为主从模式。这种典型并行处理业务模式的分布式架构具备较好的可伸缩性(可伸缩性实际上是和并行算法以及并行计算机体系结构放在一起讨论的。某个算法在某个机器上的可扩放性反映该算法是否能有效利用不断增加的CPU。)和稳定性。主进程不负责具体的业务处理,而是负责调度和管理工作进程,工作进程负责具体的业务处理,所以,工作进程的稳定性是开发人员需要关注的。


PM2内置的负载均衡

PM2内置的负载平衡器可以在所有可用的CPU上扩展已经联网的Node.js应用(http(s)/tcp/udp服务器),不需要修改代码。


使用

使用-i 选项来开启集群模式:

pm2 start app.js -i max


max表示PM2会自动检测可用CPU的数量并扩展相同数量的进程。

或者通过生态系统文件(ecosystem.config.js)配置:

module.exports = {
  apps: [{
    script: "app.js",
    instances: "max",
  }]
}

instances选项可以是:

一个整数。指定开启集群的进程数量。

max。 自动检测可用CPU的数量并运行相同数量的进程。

instances也可以是一个负整数。 如果有4个核心,pm2 start -i -1会扩展3个群集(最大整数)。


无状态应用

在群集环境中,你首先要确保你的应用进程没有内部状态。

内部状态通常是存储在进程中的一些本地数据。 例如,它可以是一组websocket的连接或本地session。 可以使用Redis或其他数据库来共享进程间的状态。


创建无状态应用教程

一个无状态应用实质进程内没有保存本地数据,例如websocket的连接或本地session和相关的数据。

可以使用Redis,Mongo或其他数据库来共享进程间的状态。

关于如何编写高效的、生产就绪的无状态应用,这里有个关于十二因素应用宣言的有用的资源 https://12factor.net


不宕机重载

使用restart命令时,pm2先杀掉进程然后重启进程,所以重启的这段时间是无法使用服务的。

但是使用reload命令,pm2滚动重启所有进程,并会保持至少有一个进程正在运行:

pm2 reload 

或者:

pm2 reload ecosystem.config.js
pm2 reload ecosystem.config.js --only app

如果reload应用进程超时,将会退回使用restart。


优雅开机和关机

为了确保所有请求在重载中都可以被正确处理,你要确保应用进程关闭时已经处理完所有请求,不能留下未响应的请求。

优雅的关机确保在退出应用程序之前处理所有剩余的查询并关闭所有外部连接。

可以查看如何设置正常关机的教程。


集群环境变量

环境变量NODE_APP_INSTANCE用于区分不同的集群。

例如,如果你想只在一个集群上运行一个cronjob,可以判断process.env.NODE_APP_INSTANCE === 0是否成立。

该变量可在生态系统文件(ecosystem.config.js)中重命名:

module.exports = {
apps: [{
    name: "app",
    script: "./app.js",
    instance_var: "INSTANCE_ID",
  }]
}

这对使用node-config包时报告命名冲突是有用的,查看问题。




参考:

https://zhuanlan.zhihu.com/p/32841479

https://github.com/shipengqi/PM2-docs-Zh-CN/blob/master/guide/load_balancing.md

https://www.zhihu.com/question/59818903/answer/452184131

https://segmentfault.com/a/1190000007343993

声明:本站所有文章和图片,如无特殊说明,均为原创发布。商业转载请联系作者获得授权,非商业转载请注明出处。
随机推荐
Node.js 实现 RBAC 权限模型
Express 使用 cookie-session 处理 session
WordPress 函数 get_categories() 获取所有分类
JavaScript 修改内容和属性
Node.js 控制台进度条实现原理
Express 使用 body-parser 处理 HTTP 请求
JavaScript navigator对象
JavaScript 事件