標籤:des style blog http color io os 使用 ar
引子
眾所周知,雖然Node的底層有一個IO線程池,但其應用程式層預設是單線程啟動並執行,對於多核CPU環境來說,是一種資源的浪費。
所幸Node提供了child_process 模組,讓開發人員得以開多個進程,實現每個進程各自利用一個CPU,以實現多核的利用。
child_process 模組給予Node 可以隨意建立子進程的能力。因為 child_process 類本身是一個 EventEmitter,所以處理序間通訊很容易;且父子處理序間通訊並不通過網路層,而是在核心中完成,高效。
但 child_process 對於開發人員來說,編程模型還是過於複雜,需要操心的細節過多,比如:父進程崩潰了,子進程回收是需要開發人員提供代碼來處理的——如果開發人員只是想單純利用多核模型,對具體背景工作處理序的控制粒度並沒有太多設想,那這種開發模型無疑是令人頭疼的。
針對這個問題,Node 提供了 Cluster 模組。Cluster 簡化了父子模型編程模型,只區分:當前進程是不是 Master,是 Master 就可以fork子進程,不是那就請行使Worker 職責。至於什麼資源的回收,負載的調配,uncaughtException的處理,它自有安排。
本質上, Cluster 是 child_process 和 net 模組的組合應用。它不僅簡化了編程模型,還使得共用監聽同一連接埠成為可能。
更多關於Cluster的原理這裡不表,感興趣可以移步 → 解讀Nodejs多核處理模組cluster
情境
用Node快速搭站呢,當然就用 express 咯 所以,需求就是用 Cluster 做個單機叢集,多進程跑 express,提升網站的輸送量。
實驗
先用 express-generator 搭個框子,模板引擎還是使 ejs 吧:
express -e myapp2
express-generator 預設會把 app.js 產生好,一行代碼不寫,一個完備的 http server 是已經實現的了。So,我們可以實現一個 Cluster 單機叢集,clusterMaster.js:
//CPU幾核?var cpus = require(‘os‘).cpus().length; //子進程監聽訊息處理函數var workerListener = function (msg) { if (msg.access) console.log(‘user access %s, worker [%d]‘, msg.access, msg.workerid);};//fork新的子進程函數var forkWorker = function(listener){ var worker = cluster.fork(); console.log(‘worker [%d] has been created‘, worker.process.pid); worker.on(‘message‘, listener); return worker;}; //Cluster處理var cluster = require(‘cluster‘);if (cluster.isMaster) { for (var i = 0; i < cpus; i++) { forkWorker(workerListener); }} else { var app = require(‘./app‘); return app.listen(3000);} //Cluster收到子進程退出訊息cluster.on(‘exit‘, function (worker, code, signal) { console.log(‘worker [%d] died %s, fork a new one.‘, worker.process.pid, code || signal); forkWorker(workerListener);});//Cluster收到子進程運行訊息cluster.on(‘online‘, function(worker){ console.log(‘worker [%d] is running.‘, worker.process.pid);});
可以看到:
- Cluster 是通過 isMaster 來區分父子進程的。父進程中處理建立邏輯:根據 CPU的核心數,建立相應數量的子進程;子進程中運行具體的 Server 建立代碼。夠簡單的模型~
- 在 Cluster 父進程端,是始終能獲知 當前worker 的。而當前 worker 的 pid 呢,在有worker 控制代碼情況下,對應的是 woker.process.pid;在子進程啟動並執行上下文中,就是 process.pid。這不難理解~
- 我們為新fork出來的子進程添加了訊息處理函數,使得任何子進程運行代碼,都可以隨時利用訊息觸發它:用途挺廣泛的,稍後會示範一個例子
- 在子進程退出時,Cluster 父進程會收到 exit 訊息,這時會重fork一個新子進程來補缺
假設使用者訪問這個網站: localhost:3000/,我希望告訴他,是哪個子進程在為他渲染頁面。那麼,在 /routes/ 添加一個express 路由 index.js:
var express = require(‘express‘);var router = express.Router(); /* GET home page. */router.get(‘/‘, function (req, res) { res.render(‘index‘, { title: ‘Express‘, workerid: process.pid }); process.send({access: ‘/‘, workerid: process.pid});}); module.exports = router;
可以看到,除了路由,還使用 process.send 發送了訊息,它實際上會觸發之前代碼中子進程註冊的 workerListener,向它彙報路徑和進程號;由於使用了 render,那自然是少不了 ejs 模板, /views/index.js:
<!DOCTYPE html><html> <head> <title><%= title %></title> <link rel=‘stylesheet‘ href=‘/stylesheets/style.css‘ /> </head> <body> <h1><%= title %></h1> <p>Welcome to <%= title %></p> <p>[<%= workerid %>] worker is serving for you.</p> </body></html>
模板會將 render 攜帶的子進程 pid 渲染到頁面上; 當然少不了在 app.js 中添加路由映射:
var routes = require(‘./routes/index‘); app.use(‘/‘, routes);
運行程式,在 shell 下 看看進程產生情況:
一父四子,試著用 kill 64163殺死一個子進程。可以在console看到,主進程收到了 exit訊息,並重新fork一個新的進程:
測試一下負載,用 Chrome 重複訪問幾次網站,似乎 worker 子進程 一直未變:
用 ab 來類比一下並發,讓它一個核忙不過來:
換 Safari 瀏覽器訪問一下網站,這次換了一個 worker 進程來服務,速度還不慢
更多文章請移步我的blog新地址: http://www.moye.me/
[Node.js] Cluster,把多核用起來