使用node的時候,不得不關注node的兩個缺點。
- 如何充分利用多核cpu的計算能力。
- 如何保證進程的健壯性和穩定性。
上面的兩個缺點是把node放在服務器的場景中考慮的。
對于第一個問題,雖然node底層c++是可以使用多線程的,但是因為大部分時候使用node是因為希望使用JavaScript,就不討論這一部分。我們知道JavaScript代碼是單線程的,所以這里想利用多核cpu,就不得不討論多進程了。
第二個問題,由于單線程或者多線程的程序,一旦線程的異常沒有處理,將會引起進程的崩潰,導致服務不可用,所以這里主要討論進程的自動重啟的方法。
node多進程
為了充分利用cpu資源,或者僅僅是為了使用node創建另一個進程(子進程會復制父進程的內存空間,所以父進程加載過的模塊子進程就不需要自己重新加載),我們可以使用node的child_process
模塊。下圖是node創建多進程的一個示意圖。
當創建多個進程后,還可以進行進程間通信,通過父進程管理子進程,這樣,web服務器的使用場景中,我們不需要創建很多進程,只需要創建和cpu數目相同的進程,就可以使用node的高并發特性,又能充分利用到cpu的多核并行計算特性。
通過上圖我們可以知道node利用libuv庫來使用操作系統的進程間通信功能。
node多進程實例
上面我們知道node多進程架構的基本結構和原理后,來看一個具體的例子。由于node最初是為了做高性能web服務器的,所以我們看一個和網絡相關的例子。
下面通過node實現多個進程監聽一個端口(比如80端口),當有用戶請求服務時,某一個進程可以響應該請求。
-
創建parent.js
master進程只負責創建孩子,這樣master進程邏輯簡單,性能好,不容易崩潰。
var cp = require('child_process');
var child1 = cp.fork('child.js');
var child2 = cp.fork('child.js');
// Open up the server object and send the handle.
var server = require('net').createServer();
server.listen(1337, function () {
child1.send('server', server);
child2.send('server', server);
server.close(); //parent不處理請求
});
-
創建child.js
work進程接受http請求,處理請求并返回。由于監聽同一個端口,不占用很多文件句柄,操作系統可以允許創建很多個這樣的進程。
// child.js
var http = require('http');
//The callback is a function which is automatically added to the 'request' event.
var server = http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('handled by child, pid is ' + process.pid + '\n');
});
process.on('message', function (m, tcp) {
if (m === 'server') {
tcp.on('connection', function (socket) {
server.emit('connection', socket); //模擬一個http的connection事件
});
}
});
- 測試例子
curl "http://127.0.0.1:1337/"
會返回handled by child, pid is XXXX
上面例子的要點:
- 通過
child1.send('server', server);
傳遞一個server(tcp)用來監聽端口。 - 因為不同進程使用的是同一個server,可以監聽同一個端口。注意這個底層的機制是
SO_ REUSEADDR
- 當請求響應的來到的時候,操作系統決定哪一個進程處理響應。為搶占式。
穩定性
上面的例子已經可以使用多核cpu了,那么來解決第二個問題,如何處理健壯性問題。比如
- 狀態管理
- 平滑重啟
- 配置動態載入
由于寫一個這樣的模塊并不是簡單的任務,或者簡單的謝謝也沒有什么用,我們直接看一個開源例子PM2。
下面是pm2的功能列表,可以看到他有cluster mode等十幾項功能。
當然一些自動重啟等最基本的功能肯定可以勝任。
多臺服務器
上面并沒有提到把工作進程放到不同的機器上,實際上這是有必要的,比如redis需要內存大的機器,數據庫系統需要磁盤好的機器,業務邏輯需要cpu好的機器。對于這個方面,可以考慮網絡通信方式。推薦zmq
總結
可以看到JavaScript也可以利用多核cpu的強大性能,并且提供了方便的進程間通信方法(父子進程間)。多機之間也可以利用現有的網絡通信機制進行通信。多進程管理方面也有一些開源框架提供了支持。總的來說,在web服務器方面的應用是成熟可靠的。
本文引用了
《深入淺出node.js》
http://pm2.keymetrics.io/
http://nodejs.cn/doc/node/child_process.html
http://nodejs.cn/doc/node/cluster.html