為什么要用cluster?
Node.js是一個單線程單進程模型,它是基於事件循環機制來進行調度處理,當有事件發生時,響應的callback就會被觸發, 但是在任何時候,只會有一個callback被執行。當callback執行時間過長,這勢必會對隊列中其他請求造成影響。更嚴重的是, 如果處理某個請求時產生一個沒有被捕獲到的異常導致整個進程的退出,已經接收到的請求都將無法被處理。 如何使node.js支持多線程或多進程?如何使node.js充分利用多核的cpu?
cluster介紹
cluster是node.js的內置模塊,它在V0.8就已經被引入,它是一種多進程的解決方案。cluster允許node.js應用有效的利用多核的cpu成為可能,它極大提高了應用的性能和可靠性。它只需要一個fork,不需要開發者修改任何代碼便能夠實現多進程部署。
cluster是如何工作的?
cluster對child_process模塊提供了一層封裝,各個工作子進程都是由child_process.fork()創建的。child_process也是node.js的一個內置模塊,child_process.fork()專門用於衍生新的node.js進程,它會返回一個childprocess對象,返回的child_process會有一個額外的內置的通信通道,它允許消息在父進程和子進程之間來回傳遞。衍生的 Node.js 子進程在其兩兩之間建立的 IPC 通信信道的異常是獨立於父進程的。 每個進程都有自己的內存,使用自己的 V8 實例。 由於需要額外的資源分配,因此不推薦衍生大量的 Node.js 進程。
cluster的屬性和方法
- cluster.isMaster 當該進程時主進程時,返回true
- cluster.isWorker 當進程不是主進程時,返回true
- cluster.worker 當前工作進程的引用,對於主進程則無效
- cluster.workers 返回cluster中所有的工作進程對象
- cluster.fork() 衍生一個新的工作進程
- cluster.disconnect([callback]) 斷開當前的工作進程
cluster的事件
- Event:’fork’ 監聽worker創建事件
- Event:’online’ 監聽worker創建成功事件
- Event:’listening’ 監聽worker進入監聽事件
- Event:’disconnect’ 監聽worker斷開事件
- Event:’exit’ 監聽worker退出事件
- Event:’mesage’ 監聽worker進程發送的消息事件
在js代碼中使用cluster
可以看看官方網站上的實例:
const cluster = require('cluster'); const http = require('http'); const numCPUs = require('os').cpus().length; if (cluster.isMaster) { console.log(`主進程 ${process.pid} 正在運行`); // 衍生工作進程。 for (let i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', (worker, code, signal) => { console.log(`工作進程 ${worker.process.pid} 已退出`); }); } else { // 工作進程可以共享任何 TCP 連接。 // 在本例子中,共享的是一個 HTTP 服務器。 http.createServer((req, res) => { res.writeHead(200); res.end('你好世界\n'); }).listen(8000); console.log(`工作進程 ${process.pid} 已啟動`); }
每一次調用fork()函數時,都會產生一個新的工作進程。在上面的代碼示例中,先檢查當前的工作進程是否是主工作進程,如果是,則創建子工作進程;如果當前進程不是主進程,fork方法會啟動一個新的node.js進程,這些進程的代碼都是相同的。 運行代碼之后,Node.js 將會在工作進程(指代子進程)之間共享8000端口。
在typescript中使用cluster
這里我們使用之前的一個nest.js的項目為例:
import * as cluster from "cluster"; import { AppRun } from './appRun'; import { cpus } from "os"; const numCPUs = cpus().length; if (cluster.isMaster) { console.log(`This machine has ${numCPUs} CPUs.`); for (let i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on("online", (worker) => { console.log(`Worker ${worker.process.pid} is online`); }); cluster.on("exit", (worker, code, signal) => { console.log(`Worker ${worker.process.pid} died with code: ${code} and signal: ${signal}`); console.log("Starting a new worker..."); cluster.fork(); }); } else { console.log('else'); const app = new AppRun(3002); app.start(); }
appRun.ts就是之前的啟動文件main.ts的改造:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; export class AppRun { private port: number; constructor(port: number) { this.port = port; } async start() { const app = await NestFactory.create(AppModule); await app.listen(this.port, () => { console.log(`app run :${process.pid} isten on port ${this.port}`) }); } }
上述就是使用node.js的cluster模塊來實現多進程的方案。還有一種解決方案就是node.js+pm2,pm2集成了cluster模塊,用戶可以無需更改代碼,只需要在部署時使用pm2來啟動應用程序,即可實現多進程擴展。