[TOC]
## 概述
### 基本用法
Node.js默認單進程運行,對于多核CPU的計算機來說,這樣做效率很低,因為只有一個核在運行,其他核都在閑置。cluster模塊就是為了解決這個問題而提出的。
cluster模塊允許設立一個主進程和若干個worker進程,由主進程監控和協調worker進程的運行。worker之間采用進程建通信交換消息,cluster模塊內置一個負載均衡器,采用Round-robin算法協調各個worker進程之間的負載。運行時,所有新建立的鏈接都由主進程完成,然后主進程再把TCP連接分配給指定的worker進程。
~~~
var cluster = require('cluster');
var os = require('os');
if (cluster.isMaster){
for (var i = 0, n = os.cpus().length; i < n; i += 1){
cluster.fork();
}
} else {
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
}
~~~
上面代碼先判斷當前進程是否為主進程(cluster.isMaster),如果是的,就按照CPU的核數,新建若干個worker進程;如果不是,說明當前進程是worker進程,則在該進程啟動一個服務器程序。
### cluster.worker對象
cluster.worker指向當前worker進程對象,主進程沒有這個值。
它有如下屬性。
(1)worker.id
work.id返回當前worker的獨一無二的進程編號。這個編號也是cluster.workers中指向當前進程的索引值。
(2)worker.process
所有的worker進程都是用child_process.fork()生成的。child_process.fork()返回的對象,就被保存在worker.process之中。通過這個屬性,可以獲取worker所在的進程對象。
(3)worker.send()
該方法用于在主進程中,向子進程發送信息。
~~~
if (cluster.isMaster) {
var worker = cluster.fork();
worker.send('hi there');
} else if (cluster.isWorker) {
process.on('message', function(msg) {
process.send(msg);
});
}
~~~
上面代碼的作用是,worker進程對主進程發出的每個消息,都做回聲。
在worker進程中調用這個方法,等同于process.send(message)。
### cluster.workers對象
該對象只有主進程才有,包含了所有worker進程。每個成員的鍵值就是一個worker進程,鍵名就是該worker進程的worker.id屬性。
~~~
function eachWorker(callback) {
for (var id in cluster.workers) {
callback(cluster.workers[id]);
}
}
eachWorker(function(worker) {
worker.send('big announcement to all workers');
});
~~~
上面代碼用來遍歷所有worker進程。
當前socket的data事件,也可以用id屬性識別worker進程。
~~~
socket.on('data', function(id) {
var worker = cluster.workers[id];
});
~~~
## 屬性與方法
### isMaster,isWorker
isMaster屬性返回一個布爾值,表示當前進程是否為主進程。這個屬性由process.env.NODE_UNIQUE_ID決定,如果process.env.NODE_UNIQUE_ID為未定義,就表示該進程是主進程。
isWorker屬性返回一個布爾值,表示當前進程是否為work進程。它與isMaster屬性的值正好相反。
### fork()
fork方法用于新建一個worker進程,上下文都復制主進程。只有主進程才能調用這個方法。
該方法返回一個worker對象。
### kill()
kill方法用于終止worker進程。它可以接受一個參數,表示系統信號。
如果當前是主進程,就會終止與worker.process的聯絡,然后將系統信號法發向worker進程。如果當前是worker進程,就會終止與主進程的通信,然后退出,返回0。
在以前的版本中,該方法也叫做 worker.destroy() 。
### listening事件
worker進程調用listen方面以后,“listening”就傳向該進程的服務器,然后傳向主進程。
該事件的回調函數接受兩個參數,一個是當前worker對象,另一個是地址對象,包含網址、端口、地址類型(IPv4、IPv6、Unix socket、UDP)等信息。這對于那些服務多個網址的Node應用程序非常有用。
~~~
cluster.on('listening', function(worker, address) {
console.log("A worker is now connected to " + address.address + ":" + address.port);
});
~~~
## 實例:不中斷地重啟Node服務
重啟服務需要關閉后再啟動,利用cluster模塊,可以做到先啟動一個worker進程,再把原有的所有work進程關閉。這樣就能實現不中斷地重啟Node服務。
下面是主進程的代碼master.js。
~~~
var cluster = require('cluster');
console.log('started master with ' + process.pid);
// 新建一個worker進程
cluster.fork();
process.on('SIGHUP', function () {
console.log('Reloading...');
var new_worker = cluster.fork();
new_worker.once('listening', function () {
// 關閉所有其他worker進程
for(var id in cluster.workers) {
if (id === new_worker.id.toString()) continue;
cluster.workers[id].kill('SIGTERM');
}
});
});
~~~
上面代碼中,主進程監聽SIGHUP事件,如果發生該事件就關閉其他所有worker進程。之所以是SIGHUP事件,是因為nginx服務器監聽到這個信號,會創造一個新的worker進程,重新加載配置文件。另外,關閉worker進程時,主進程發送SIGTERM信號,這是因為Node允許多個worker進程監聽同一個端口。
下面是worker進程的代碼server.js。
~~~
var cluster = require('cluster');
if (cluster.isMaster) {
require('./master');
return;
}
var express = require('express');
var http = require('http');
var app = express();
app.get('/', function (req, res) {
res.send('ha fsdgfds gfds gfd!');
});
http.createServer(app).listen(8080, function () {
console.log('http://localhost:8080');
});
~~~
使用時代碼如下。
~~~
$ node server.js
started master with 10538
http://localhost:8080
~~~
然后,向主進程連續發出兩次SIGHUP信號。
~~~
$ kill -SIGHUP 10538
$ kill -SIGHUP 10538
~~~
主進程會連續兩次新建一個worker進程,然后關閉所有其他worker進程,顯示如下。
~~~
Reloading...
http://localhost:8080
Reloading...
http://localhost:8080
~~~
最后,向主進程發出SIGTERM信號,關閉主進程。
~~~
$ kill 10538
~~~
## PM2模塊
PM2模塊是cluster模塊的一個包裝層。它的作用是盡量將cluster模塊抽象掉,讓用戶像使用單進程一樣,部署多進程Node應用。
~~~
// app.js
var http = require('http');
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world");
}).listen(8080);
~~~
上面代碼是標準的Node架設Web服務器的方式,然后用PM2從命令行啟動這段代碼。
~~~
$ pm2 start app.js -i 4
~~~
上面代碼的i參數告訴PM2,這段代碼應該在cluster_mode啟動,且新建worker進程的數量是4個。如果i參數的值是0,那么當前機器有幾個CPU內核,PM2就會啟動幾個worker進程。
如果一個worker進程由于某種原因掛掉了,會立刻重啟該worker進程。
~~~
# 重啟所有worker進程
$ pm2 reload all
~~~
每個worker進程都有一個id,可以用下面的命令查看單個worker進程的詳情。
~~~
$ pm2 show <worker id>
~~~
正確情況下,PM2采用fork模式新建worker進程,即主進程fork自身,產生一個worker進程。`pm2 reload`命令則會用spawn方式啟動,即一個接一個啟動worker進程,一個新的worker啟動成功,再殺死一個舊的worker進程。采用這種方式,重新部署新版本時,服務器就不會中斷服務。
~~~
$ pm2 reload <腳本文件名>
~~~
關閉worker進程的時候,可以部署下面的代碼,讓worker進程監聽shutdown消息。一旦收到這個消息,進行完畢收尾清理工作再關閉。
~~~
process.on('message', function(msg) {
if (msg === 'shutdown') {
close_all_connections();
delete_logs();
server.close();
process.exit(0);
}
});
~~~
## 參考鏈接
* José F. Romaniello,?[Reloading node with no downtime](http://joseoncode.com/2015/01/18/reloading-node-with-no-downtime/)
* Joni Shkurti,?[Node.js clustering made easy with PM2](https://keymetrics.io/2015/03/26/pm2-clustering-made-easy/)
- 第一章 導論
- 1.1 前言
- 1.2 為什么學習JavaScript?
- 1.3 JavaScript的歷史
- 第二章 基本語法
- 2.1 語法概述
- 2.2 數值
- 2.3 字符串
- 2.4 對象
- 2.5 數組
- 2.6 函數
- 2.7 運算符
- 2.8 數據類型轉換
- 2.9 錯誤處理機制
- 2.10 JavaScript 編程風格
- 第三章 標準庫
- 3.1 Object對象
- 3.2 Array 對象
- 3.3 包裝對象和Boolean對象
- 3.4 Number對象
- 3.5 String對象
- 3.6 Math對象
- 3.7 Date對象
- 3.8 RegExp對象
- 3.9 JSON對象
- 3.10 ArrayBuffer:類型化數組
- 第四章 面向對象編程
- 4.1 概述
- 4.2 封裝
- 4.3 繼承
- 4.4 模塊化編程
- 第五章 DOM
- 5.1 Node節點
- 5.2 document節點
- 5.3 Element對象
- 5.4 Text節點和DocumentFragment節點
- 5.5 Event對象
- 5.6 CSS操作
- 5.7 Mutation Observer
- 第六章 瀏覽器對象
- 6.1 瀏覽器的JavaScript引擎
- 6.2 定時器
- 6.3 window對象
- 6.4 history對象
- 6.5 Ajax
- 6.6 同域限制和window.postMessage方法
- 6.7 Web Storage:瀏覽器端數據儲存機制
- 6.8 IndexedDB:瀏覽器端數據庫
- 6.9 Web Notifications API
- 6.10 Performance API
- 6.11 移動設備API
- 第七章 HTML網頁的API
- 7.1 HTML網頁元素
- 7.2 Canvas API
- 7.3 SVG 圖像
- 7.4 表單
- 7.5 文件和二進制數據的操作
- 7.6 Web Worker
- 7.7 SSE:服務器發送事件
- 7.8 Page Visibility API
- 7.9 Fullscreen API:全屏操作
- 7.10 Web Speech
- 7.11 requestAnimationFrame
- 7.12 WebSocket
- 7.13 WebRTC
- 7.14 Web Components
- 第八章 開發工具
- 8.1 console對象
- 8.2 PhantomJS
- 8.3 Bower:客戶端庫管理工具
- 8.4 Grunt:任務自動管理工具
- 8.5 Gulp:任務自動管理工具
- 8.6 Browserify:瀏覽器加載Node.js模塊
- 8.7 RequireJS和AMD規范
- 8.8 Source Map
- 8.9 JavaScript 程序測試
- 第九章 JavaScript高級語法
- 9.1 Promise對象
- 9.2 有限狀態機
- 9.3 MVC框架與Backbone.js
- 9.4 嚴格模式
- 9.5 ECMAScript 6 介紹
- 附錄
- 10.1 JavaScript API列表
- 草稿一:函數庫
- 11.1 Underscore.js
- 11.2 Modernizr
- 11.3 Datejs
- 11.4 D3.js
- 11.5 設計模式
- 11.6 排序算法
- 草稿二:jQuery
- 12.1 jQuery概述
- 12.2 jQuery工具方法
- 12.3 jQuery插件開發
- 12.4 jQuery.Deferred對象
- 12.5 如何做到 jQuery-free?
- 草稿三:Node.js
- 13.1 Node.js 概述
- 13.2 CommonJS規范
- 13.3 package.json文件
- 13.4 npm模塊管理器
- 13.5 fs 模塊
- 13.6 Path模塊
- 13.7 process對象
- 13.8 Buffer對象
- 13.9 Events模塊
- 13.10 stream接口
- 13.11 Child Process模塊
- 13.12 Http模塊
- 13.13 assert 模塊
- 13.14 Cluster模塊
- 13.15 os模塊
- 13.16 Net模塊和DNS模塊
- 13.17 Express框架
- 13.18 Koa 框架