共计 4106 个字符,预计需要花费 11 分钟才能阅读完成。
近期有需要须要理解 PM2 一些性能的实现形式,所以趁势看了一下 PM2 的源码,也算是用了这么多年的 PM2,第一次进入外部进行一些摸索。
PM2 是一个 基于 node.js 的过程管理工具,自身 node.js 是一个单过程的语言,然而 PM2 能够实现多过程的运行及治理(当然还是基于 node 的 API),还提供程序零碎信息的展现,包含 内存、CPU 等数据。
PM2 的外围性能概览
源码地位
官方网站
PM2 的性能、插件十分的丰盛,但比拟外围的性能其实不多:
- 多过程治理
- 零碎信息监控
- 日志治理
其余的一些性能就都是基于 PM2 之上的辅助性能了。
我的项目构造
PM2 的我的项目构造算是比拟简洁的了,次要的源码都在 lib
目录下,God
目录为外围性能多过程治理的实现,以及 API
目录则是提供了各种能力,包含 日志治理、面板查看零碎信息以及各种辅助性能,最初就是 Sysinfo
目录下对于如何采集零碎信息的实现了。
# 删除了多个不相干的文件、文件夹
lib
├── API # 日志治理、GUI 等辅助性能
├── God # 多过程治理逻辑实现地位
└── Sysinfo # 零碎信息采集
几个比拟要害的文件作用:
-
Daemon.js
- 守护过程的次要逻辑实现,包含 rpc server,以及各种守护过程的能力
-
God.js
- 业务过程的包裹层,负责与守护过程建设连贯,以及注入一些操作,咱们编写的代码最终是由这里执行的
-
Client.js
- 执行 PM2 命令的次要逻辑实现,包含与守护过程建设 rpc 连贯,以及各种申请守护过程的操作
-
API.js
- 各种功能性的实现,包含启动、敞开我的项目、展现列表、展现零碎信息等操作,会调用 Client 的各种函数
-
binaries/CLI.js
- 执行 pm2 命令时候触发的入口文件
守护过程与 Client 过程通信形式
看源码后会晓得,PM2 与 Client 过程(也就是咱们 pm2 start XXX
时对应的过程),是通过 RPC 进行通信的,这样就能保障所有的 Client 过程能够与守护过程进行通信,上报一些信息,以及从守护过程层面执行一些操作。
PM2 启动程序的形式
PM2 并不是简略的应用 node XXX 来启动咱们的程序,就像前边所提到了守护过程与 Client 过程的通信形式,Client 过程会将启动业务过程所须要的配置,通过 rpc 传递给守护过程,由守护过程去启动程序。
这样,在 PM2 start 命令执行实现当前业务过程也在后盾运行起来了,而后等到咱们后续想再针对业务过程进行一些操作的时候,就能够通过列表查看对应的 pid、name 来进行对应的操作,同样是通过 Client 触发 rpc 申请到守护过程,实现逻辑。
当然,咱们其实很少会有独自启动守护过程的操作,守护过程的启动其实被写在了 Client 启动的逻辑中,在 Client 启动的时候会查看是否有存活的守护过程,如果没有的话,会尝试启动一个新的守护过程用于后续的应用。
具体形式就是通过 spawn
+ detached: true
来实现的,创立一个独自的过程,这样即使是咱们的 Client 作为父过程退出了,守护过程仍然是能够独立运行在后盾的。
P.S. 在应用 PM2 的时候应该有时也会看到有些这样的输入,这个其实就是 Client 运行时监测到守护过程还没有启动,被动启动了守护过程:
> [PM2] Spawning PM2 daemon with pm2_home=/Users/jiashunming/.pm2
> [PM2] PM2 Successfully daemonized
多过程治理
个别应用 PM2 实现多过程治理次要的目标是为了可能让咱们的 node 程序能够运行在多核 CPU 上,比方四核机器,咱们就心愿可能存在四个过程在运行,以便更高效的反对服务。
在过程治理上,PM2 提供了一个大家常常会用到的参数:exec_mode
,它的取值只有两个,cluster
和 fork
,fork
是一个比拟惯例的模式,相当于就是执行了屡次的 node XXX.js
。
然而这样去运行 node 程序就会有一个问题,如果是一个 HTTP 服务的话,很容易就会呈现端口抵触的问题:
const http = require('http')
http.createServer(() => {}).listen(8000)
比方咱们有这样的一个 PM2 配置文件,那么执行的时候你就会发现,报错了,提醒端口抵触:
module.exports = {
apps: [
{
// 设置启动实例个数
"instances": 2,
// 设置运行模式
"exec_mode": "fork",
// 入口文件
"script": "./test-create-server.js"
}
]
}
这是因为在 PM2 的实现中,fork 模式下就是简略的通过 spawn 执行入口文件罢了。
实现地位:lib/God/ForkMode.js
而当咱们把 exec_mode
改为 cluster
之后,你会发现程序能够失常运行了,并不会呈现端口占用的谬误。
这是因为 PM2 应用了 node 官网提供的 cluster 模块来运行程序。
cluster 是一个 master-slave 模型的运行形式(_最近 ms 这个说法貌似变得不政治正确了。。_),首先须要有一个 master 过程来负责创立一些工作过程,或者叫做 worker 吧。
而后在 worker 过程中执行 createServer 监听对应的端口号即可。
const http = require('http')
const cluster = require('cluster')
if (cluster.isMaster) {
let limit = 2
while (limit--) {cluster.fork()
}
} else {http.createServer((req, res) => {res.write(String(process.pid))
res.end()}).listen(8000)
}
详情能够参考 node.js 中 TCP 模块对于 listen 的实现:lib/net.js
在外部实现逻辑大抵为,master 过程负责监听端口号,并通过 round_robin 算法来进行申请的散发,master 过程与 worker 过程之间会通过基于 EventEmitter 的音讯进行通信。
具体的逻辑实现都在这里 lib/internal/cluster 因为是 node 的逻辑,并不是 PM2 的逻辑,所以就不太多说了。
而后回到 PM2 对于 cluster 的实现,其实是设置了 N 多的默认参数,而后增加了一些与过程之间的 ipc 通信逻辑,在过程启动胜利、出现异常等非凡状况时,进行对应的操作。
因为前边也提到了,PM2 是由守护过程保护治理所有的业务过程的,所以守护过程会保护与所有服务的连贯。process
对象是继承自 EventEmitter
的,所以咱们只是监听了一些特定的事件,包含 uncaughtException
、unhandledRejection
等。
在过程重启的实现形式中,就是由子过程监听到异样事件,向守护过程发送异样日志的信息,而后发送 disconnect
示意过程行将退出,最初触发本身的 exit
函数终止掉过程。
同时守护过程在接管到音讯当前,也会从新创立新的过程,从而实现了过程主动重启的逻辑。
实现业务过程的次要逻辑在 lib/ProcessContainer 中,它是咱们理论代码执行的载体。
零碎信息监控
零碎信息监控这块,在看源码之前认为是用什么 addon 来做的,或者是某些黑科技。
然而真的循着源码看上来,发现了就是用了 pidusage 这个包来做的 - –
只关怀 unix 零碎的话,外部实际上就是 ps -p XXX
这么一个简略的命令。
至于在应用 pm2 monit
、pm2 ls --watch
命令时,实际上就是定时器在循环调用上述的获取零碎信息办法了。
具体实现逻辑:
getMonitorData
dashboard
list
后边就是如何应用基于终端的 UI 库展示数据的逻辑了。
日志治理
日志在 PM2 中的实现分了两块。
一个是业务过程的日志、还有一个是 PM2 守护过程本身的日志。
守护过程的日志实现形式是通过 hack 了 console
相干 API 实现的,在原有的输入逻辑根底上增加了一个基于 axon 的消息传递,是一个 pub/sub 模型的,次要是用于 Client 取得日志,例如 pm2 attach
、pm2 dashboard
等命令。
业务过程的日志实现形式则是通过笼罩了 process.stdout
、process.stderr
对象上的办法(console
API 基于它实现),在接管到日志当前会写入文件,同时调用 process.send
将日志进行转发,而守护过程监听对应的数据,也会应用上述守护过程创立的 socket 服务将日志数据进行转发,这样业务过程与守护过程就有了对立的能够获取的地位,通过 Client 就能够建设 socket 连贯来实现日志的输入了。
hack console 的地位:lib/Utility.js
hack stdout/stderr write 的地位:lib/Utility.js
创立文件可写流用于子过程写入文件:lib/Utility.js
子过程接管到输入后写入文件并发送音讯到守护过程:lib/ProcessContainer.js
守护过程监听子过程音讯并转发:lib/God/ClusterMode.js
守护过程将事件通过 socket 播送:lib/Daemon.js
Client 读取并展现日志:lib/API/Extra.js
查看日志的流程中有一个小细节,就是业务日志,PM2 会先去读取文件最初的几行进行展现,而后才是根据 socket 服务返回的数据进行刷新终端展现数据。
后记
PM2 比拟外围的也就是这几块了,因为通过 Client 能够与守护过程进行交互,而守护过程与业务过程之间也存在着分割,能够执行一些操作。
所以咱们就能够很不便的对业务过程进行治理,剩下的逻辑根本就是基于这之上的一些辅助性能,以及还有就是 UI 展现上的逻辑解决了。
PM2 是一个纯 JavaScript 编写的工具,在第一次看的时候还是会感觉略显简单,到处绕来绕去的比拟晕,我举荐的一个浏览源码的形式是,通过找一些入口文件来下手,能够采纳 调试 or 加日志的形式,一步步的来看代码的执行程序。
最终就会有一个较为清晰的概念。