NO IMAGE

(1)伺服器模型

如何利用多核cpu伺服器?
多執行緒:執行緒相對程序開銷小,並且執行緒之間可以共享資料,且利用執行緒池可以減少建立和銷燬執行緒的開銷,時間片可以讓執行緒較為均勻的使用cpu資源,執行緒數量過多時,事件會被浪費在上下文切換中。
事件驅動:node和nginx都是事件驅動的
事件驅動伺服器的問題: cpu的利用率和程序的健壯性

(2)多程序架構

單程序 單執行緒 對多核使用不足的問題,建立程序,每個程序必須佔用獨立的cpu,以解決cpu佔用不足的問題。
Master-Worker模式(主從模式):分散式架構用於並行處理業務的模式,具備較好的可伸縮性和穩定性,主程序不負責具體的業務處理,而是負責排程和管理工作程序。

const child_process = require('child_process');
const cpus = require('os').cpus();
const fork = child_process.fork;
console.log(cpus.length);    // 4個cpu  開啟4個工作
for(var i=0; i<cpus.length; i  ) {
fork('./process_child');
}

通過fork複製的程序都是一個獨立的程序,這個程序中有著獨立而全新的v8例項,需要30ms的啟動時間和10M的記憶體,所以fork是昂貴的。
儘量用事件驅動方式在單執行緒上解決高併發。

1)建立子程序

型別 回撥/異常(獲知子程序的狀態) 程序型別 執行型別 可設定超時
spawn N 任意 命令 N
exec Y 任意 命令 Y
execFile Y 任意 可執行檔案 Y
fork N Node js檔案 N
linux中如果用execFile執行js檔案,必須在shell檔案 新增如下程式碼:
#!/usr/bin/env node

2)程序間通訊

前端瀏覽器中,js主執行緒與UI共用一個執行緒,WebWorker允許建立工作執行緒在後臺執行,使得阻塞嚴重的計算不影響UI渲染。

var worker = new Worker('worker.js');
worker.onmessage = function(event) {
document.getElementById('result').textContent = event.data;
}

worker.js中用 postMessage(i) 前端event.data得到資料
主執行緒與工作執行緒之間通過onmessage和postMessage進行通訊
主程序send向子程序傳送資料

const fork = cp.fork;
// 建立子程序
var n = fork(__dirname   '/process_child.js');
n.on('message', function(m) {
console.log('父程序:'   m);
});
// 向子程序發訊息
n.send({hello: 'world'});
process_child.js :
process.on('message', function(m) {
console.log('子程序: '   m);
});
process.send({foo: 'bar'});

父程序與子程序之間建立IPC通道,通過IPC通道,觸發message事件和send發訊息

程序間通訊原理:
IPC是libuv管道 在windows是命名管道 在*nix是Domain Socket
父程序在建立子程序前,會建立IPC通道並監聽它,然後建立子程序,通過環境變數高速子程序這個IPC通道的檔案描述符,子程序在啟動過程中,根據檔案描述符,去連線這個已經存在的IPC通道,完成父子間的連線。
對於其他型別的子程序,無法實現程序間通訊
3)控制代碼傳遞
兩個服務同時監聽同一個埠,第二個服務會報錯。
主程序監聽主埠,主程序對外接收所有網路請求,再將這些請求分別代理到不同的埠程序上。通過代理,可以避免埠不能重複監聽的問題,可以在代理程序上左適當的負載均衡。程序每接收一個連線,會用一個檔案描述符,作業系統的檔案描述符是有限的。

// 建立子程序
var n = fork(__dirname   '/process_child.js');
var server = require('net').createServer();
server.on('connection', function(socket) {
socket.end('父程序');
});
server.listen(3000, function() {
// 將服務發給子程序
n.send('server', server);
});
   子程序:
process.on('message', function(m, server) {
if(m == 'server') {
server.on('connection', function(socket) {
socket.end('子程序');
});
}
});

可fork多個子程序,將服務控制代碼發給子程序,然後關閉主程序服務,此時,多個程序可以監聽同一埠。
控制代碼型別:
tcp套接字
tcp伺服器
net.native c 層面的tcp套接字或ipc管道
Dgram.Socket udp套接字
Dgram.Native c 層面的udp套接字

控制代碼的傳送與還原:
send將訊息傳送到ipc管道前,將訊息組裝成兩個物件,一個引數是handle 一個是message
傳送到IPC管道中的實際上是控制代碼檔案描述符,檔案描述符是整數值。message在寫入IPC管道時會通過JSON.stringify進行序列化,所以最終傳送到IPC通道中的資訊都是字串。
連線了IPC的子程序能讀到父程序發來的訊息,將字串通過JSON.parse解析後還原為物件,此時才觸發message,傳給應用層前還要進行過濾處理,message.cmd值如果以NODE_為字首,它將相應一個內部事件internalMessage,如果是NODE_HANDLE,將取出message.type和檔案描述符一起還原一個物件。Node中只有訊息傳遞,不會真正的傳遞物件。

埠共同監聽:
獨立的程序中,tcp伺服器端socket套接字的檔案描述符並不相同,導致監聽到相同的埠時會拋異常。
不同程序可以就相同的網絡卡和埠進行監聽,這個伺服器端套接字可以被不同的程序複用。
由於獨立啟動的程序互相之間並不知道檔案描述符,所以監聽相同埠就會失敗,但對於send傳送的控制代碼還原出來的服務而言,它們的檔案描述符是相同的,所以監聽相同的埠不會引起異常。
檔案描述符同一時間只能被某個程序所用,一個請求的服務是搶佔式的。
(3)叢集穩定之路
1)程序事件
2)自動重啟
3)負載均衡
4)狀態共享
(4)Cluster模組
解決多核cpu利用率問題

const cluster = require('cluster');
const http = require('http');
var numCpus = require('os').cpus().length;
if(cluster.isMaster) {
for(var i=0; i<numCpus; i  ) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker'   worker.process.pid   ' died');
});
}else {
http.createServer(function(req, res) {
res.writeHead(200);
res.end('hello world\n');
}).listen(3000);
}

在程序中判斷是主程序還是工作程序,主要取決於環境變數中是否有NODE_UNIQUE_ID

cluster.isWorker = ('NODE_UNIQUE_ID'  in  process.env)
cluster.isMaster = (cluster.isWorker == false)

儘量使用cluster.setupMaster({
exec: “worker.js”
});
代替cluster.fork() 實現主程序和工作程序的完全剝離
1)工作原理
cluster模組其實是child_process和net模組的組合應用,cluster啟動時,會在內部啟動tcp伺服器,在cluster.fork子程序時將這個tcp服務端socket的檔案描述符傳送給工作程序。如果程序是通過cluster.fork()複製出來的,那麼環境變數裡就會存在NODE_UNIQUE_ID,如果工作程序中存在listen偵聽網路埠的呼叫它將拿到該檔案描述符,通過SO_REUSEADDR埠重用,從而實現多個子程序共享埠。
對於普通方式啟動的程序,不存在檔案描述符傳遞共享等事情。
一個主程序只能管理一組工作程序,自行通過cp模組操作子程序更加靈活,也可自行建立多個tcp伺服器,共享多個服務端socket
2)事件
a.fork:複製一個工作程序後觸發
b.online:複製好一個工作程序後,工作程序主動發一條online訊息給主程序,主程序收到訊息後觸發
c.listening:工作程序呼叫listen(共享了服務端的socket),傳送一條listening訊息給主程序,主程序接收到訊息後觸發該事件
d.disconnect: 主程序與工作程序之間IPC斷開觸發
e.exit: 有工作程序退出時
f.setup:cluster.setupMaster執行後觸發