本篇文章帶大家詳細了解一下Node.js中的集群,介紹一下cluster 事件,希望對大家有幫助!
Node 在v0.8 時直接引入了cluster 模組,用以解決多核心CPU 的使用率問題,同時也提供了較完善的API,以處理進程的健全性問題。
cluster 模組呼叫 fork 方法來建立子進程,該方法與 child_process 中的 fork 是同一個方法。 cluster 模組採用的是經典的主從模型,cluster 會建立一個 master,然後根據你指定的數量複製出多個子程序,可以使用cluster.isMaster 屬性判斷當前行程是 master 或 worker (工作進程)。由 master 進程來管理所有的子進程,主進程不負責具體的任務處理,主要工作是負責調度和管理。
cluster 模組使用內建的負載平衡來更好地處理線程之間的壓力,該負載平衡使用了 Round-robin 演算法(也稱為循環演算法)。當使用 Round-robin 調度策略時,master accepts() 所有傳入的連接請求,然後將相應的TCP請求處理發送給選取的工作進程(該方式仍然透過 IPC 來進行通訊)。 官方使用實例如下所示
const cluster = require('cluster'); const cpuNums = require('os').cpus().length; const http = require('http'); if (cluster.isMaster) { for (let i = 0; i < cpuNums; i++){ cluster.fork(); } // 子进程退出监听 cluster.on('exit', (worker,code,signal) => { console.log('worker process died,id',worker.process.pid) }) } else { // 给子进程标注进程名 process.title = `cluster 子进程 ${process.pid}`; // Worker可以共享同一个 TCP 连接,这里是一个 http 服务器 http.createServer((req, res)=> { res.end(`response from worker ${process.pid}`); }).listen(3000); console.log(`Worker ${process.pid} started`); }
其實,cluster 模組由child_process 和net 模組的組合應用,cluster 啟動時,會在內部啟動TCP 伺服器,在cluster.fork() 子程序時,將這個TCP伺服器端socket 的檔案描述符傳送給工作進程。如果工作進程是透過cluster.fork() 複製出來的,那麼它的環境變數裡就存在NODE_UNIQUE_ID,如果工作進程中存在listen() 偵聽網路連接埠的調用,它將拿到檔案描述符,透過SO_REUSEADDR 端口重複使用,從而實現多個子進程共享連接埠。
fork:複製一個工作進程後觸發該事件;
online:複製好一個工作進程後,工作進程主動發送一條online 訊息給主進程,主進程收到訊息後,觸發該事件;
listening:工作進程中調用listen() (共享了伺服器端Socket)後,發送一條listening 訊息給主進程,主進程收到訊息後,觸發該事件;
disconnect:主進程和工作進程之間IPC 通道斷開後會觸發該事件;
cluster.on('fork', ()=> { console.log('fork 事件... '); }) cluster.on('online', ()=> { console.log('online 事件... '); }) cluster.on('listening', ()=> { console.log('listening 事件... '); }) cluster.on('disconnect', ()=> { console.log('disconnect 事件... '); }) cluster.on('exit', ()=> { console.log('exit 事件... '); }) cluster.on('setup', ()=> { console.log('setup 事件... '); })
IPC 的全名為 Inter-Process Communication,即進程間通信,進程間通信的目的是為了讓不同的進程能夠互相訪問資源並進行協調工作。 Node 中實作 IPC 通道的是管道(pipe)技術,具體實作由 libuv 提供,在 Windows 下由命名管道(named pipe)實現,*nix 系統則採用 Unix Domain Socket 實現。其變現在應用層上的進程間通訊只有簡單的 message 事件和 send 方法,使用十分簡單。
父進程在實際建立子進程之前,會建立IPC 通道並監聽它,然後才真正建立出子進程,並透過環境變數(NODE_CHANNEL_FD)告訴子進程這個IPC 頻道的檔案描述子。子進程在啟動過程中,根據檔案描述子去連接這個已存在的 IPC 通道,從而完成父子進程之間的連線。
建立連線之後的父子程序就可以進行自由通訊了。由於 IPC 通道是用命名管道或 Domain Socket 創建的,它們與網路 socket 的行為比較類似,屬於雙向通訊。不同的是它們在系統核心中就完成了進程間的通信,而不用經過實際的網路層,非常有效率。在 Node 中,IPC 通道被抽象化為 Stream 對象,在呼叫 send 時傳送資料(類似 write ),接收的訊息會透過 message 事件(類似 data)觸發給應用層。
master 和 worker 进程在 server 实例的创建过程中,是通过 IPC 通道进行通信的,那会不会对我们的开发造成干扰呢?比如,收到一堆其实并不需要关心的消息?答案肯定是不会?那么是怎么做到的呢?
Node 引入进程间发送句柄的功能,send 方法除了能通过 IPC 发送数据外,还能发送句柄,第二个参数为句柄,如下所示
child.send(meeage, [sendHandle])
句柄是一种可以用来标识资源的引用,它的内部包含了指向对象的文件描述符。例如句柄可以用来标识一个服务器端 socket 对象、一个客户端 socket 对象、一个 UDP 套接字、一个管道等。 那么句柄发送跟我们直接将服务器对象发送给子进程有没有什么差别?它是否真的将服务器对象发送给子进程?
其实 send() 方法在将消息发送到 IPC 管道前,将消息组装成两个对象,一个参数是 handle,另一个是 message,message 参数如下所示
{ cmd: 'NODE_HANDLE', type: 'net.Server', msg: message }
发送到 IPC 管道中的实际上是要发送的句柄文件描述符,其为一个整数值。这个 message 对象在写入到 IPC 管道时会通过 JSON.stringify 进行序列化,转化为字符串。子进程通过连接 IPC 通道读取父进程发送来的消息,将字符串通过 JSON.parse 解析还原为对象后,才触发 message 事件将消息体传递给应用层使用。在这个过程中,消息对象还要被进行过滤处理,message.cmd 的值如果以 NODE_ 为前缀,它将响应一个内部事件 internalMessage ,如果 message.cmd 值为 NODE_HANDLE,它将取出 message.type 值和得到的文件描述符一起还原出一个对应的对象。这个过程的示意图如下所示
在 cluster 中,以 worker 进程通知 master 进程创建 server 实例为例子。worker 伪代码如下:
// woker进程 const message = { cmd: 'NODE_CLUSTER', type: 'net.Server', msg: message }; process.send(message);
master 伪代码如下:
worker.process.on('internalMessage', fn);
在前面的例子中,多个 woker 中创建的 server 监听了同个端口 3000,通常来说,多个进程监听同个端口,系统会报 EADDRINUSE 异常。为什么 cluster 没问题呢?
因为独立启动的进程中,TCP 服务器端 socket 套接字的文件描述符并不相同,导致监听到相同的端口时会抛出异常。但对于 send() 发送的句柄还原出来的服务而言,它们的文件描述符是相同的,所以监听相同端口不会引起异常。
这里需要注意的是,多个应用监听相同端口时,文件描述符同一时间只能被某个进程所用,换言之就是网络请求向服务器端发送时,只有一个幸运的进程能够抢到连接,也就是说只有它能为这个请求进行服务,这些进程服务是抢占式的。
pm2 是 node 进程管理工具,可以利用它来简化很多 node 应用管理的繁琐任务,如性能监控、自动重启、负载均衡等。
pm2 自身是基于 cluster 模块进行封装的, 本节我们主要 pm2 的 Satan 进程、God Daemon 守护进程 以及两者之间的进程间远程调用 RPC。
撒旦(Satan),主要指《圣经》中的堕天使(也称堕天使撒旦),被看作与上帝的力量相对的邪恶、黑暗之源,是God 的对立面。
其中 Satan.js 提供程序的退出、杀死等方法,God.js 负责维持进程的正常运行,God 进程启动后一直运行,相当于 cluster 中的 Master进程,维持 worker 进程的正常运行。
RPC(Remote Procedure Call Protocol)是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A 服务器上,想要调用 B 服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。同一机器不同进程间的方法调用也属于 rpc 的作用范畴。 执行流程如下所示
每次命令行的输入都会执行一次 satan 程序,如果 God 进程不在运行,首先需要启动 God 进程。然后根据指令,Satan 通过 rpc 调用 God 中对应的方法执行相应的逻辑。
以 pm2 start app.js -i 4
为例,God 在初次执行时会配置 cluster,同时监听 cluster 中的事件:
// 配置cluster cluster.setupMaster({ exec : path.resolve(path.dirname(module.filename), 'ProcessContainer.js') }); // 监听cluster事件 (function initEngine() { cluster.on('online', function(clu) { // worker进程在执行 God.clusters_db[clu.pm_id].status = 'online'; }); // 命令行中 kill pid 会触发exit事件,process.kill不会触发exit cluster.on('exit', function(clu, code, signal) { // 重启进程 如果重启次数过于频繁直接标注为stopped God.clusters_db[clu.pm_id].status = 'starting'; // 逻辑 // ... }); })();
在 God 启动后, 会建立 Satan 和 God 的rpc链接,然后调用 prepare 方法,prepare 方法会调用 cluster.fork 来完成集群的启动
God.prepare = function(opts, cb) { // ... return execute(opts, cb); }; function execute(env, cb) { // ... var clu = cluster.fork(env); // ... God.clusters_db[id] = clu; clu.once('online', function() { God.clusters_db[id].status = 'online'; if (cb) return cb(null, clu); return true; }); return clu; }
本文从 cluster 的基本使用、事件,到 cluster 的基本实现原理,再到 pm2 如何基于 cluster 进行进程管理,带你从入门到深入原理以及了解其高阶应用,希望对你有帮助。
更多node相关知识,请访问:nodejs 教程!!
以上是手把手教你玩轉Node中的集群的詳細內容。更多資訊請關注PHP中文網其他相關文章!