node.js多進程架構

node.js是單進程應用,要充分利用多核cpu的性能,就須要用到多進程架構。node

做爲web服務器,不能多個進程建立不一樣的socket文件描述符去accept網絡請求, 有經驗的同窗知道,若是端口被佔用了,再跑一個監聽該端口的服務就會報EADDRINUSE異常。那麼問題來了,多進程架構如何去解決這個問題?web

 

咱們把多進程架構設計成典型的master-workers架構, 一個master, 多個worker。服務器

 

master-workers架構以下圖所示:網絡

 

 

咱們能夠在master進程代理accept請求而後分配給worker處理。但客戶端進程鏈接到master進程,master進程鏈接到worker進程須要用掉兩個文件描述符,會浪費掉一倍數量的文件描述符。架構

因此交由worker來accept請求會是更好的方案。socket

 

master先建立一個server監聽端口,而後經過進程間通訊,把socket文件描述符傳遞給全部的worker進程, worker進程用傳遞過來的socket文件描述符封裝成server(感官上好像是把一個server對象發送給另外一個進程,實際上是把相應的句柄封裝後,經過JSON.stringify()序列化再發送, 接收端進程還原成相應的句柄。)性能

 

而後,還有一個問題,假如其中一個worker進程異常退出了怎麼辦, 這個時候,worker進程應該要通知到master進程,而後master進程從新fork一個worker進程。ui

 

先上master的代碼:spa

 1 "use strict"
 2 
 3 const fork = require('child_process').fork;
 4 const cpus = require('os').cpus();
 5 let server = require('net').createServer((socket)=>{
 6     // ‘connection’ 監聽器
 7     socket.end('Handled by master \n');
 8     console.error('Handled by master \n');   //不該該在master accept請求
 9 });
10 
11 
12 server.listen(8001);
13 
14 let workers = {};
15 
16 function createWorker(ser) {
17     let worker = fork('./worker.js');
18 
19     worker.on('message', function(msg, handle) {
20         // 收到子進程通知須要建立新的worker(子進程退出前通知父進程)
21         if(msg ==='new_worker') {
22             let ser = handle;
23             createWorker(ser);
24             // 關掉
25             ser.close();
26         }
27     })
28 
29 
30     worker.on('exit', function(code, signal){
31         delete workers[worker.pid];
32     });
33 
34     // 句柄轉發 
35     let result = worker.send('server', ser, (err)=> {err&&console.error(err)});
36     console.info('send server to child result:', result);
37     workers[worker.pid] = worker;
38 }
39 
40 for(let i=0; i<cpus.length; i++) {
41     createWorker(server);
42 }
43 
44 // 關掉,再也不accept端口請求
45 server.close();
46 
47 /*
48 code <number> The exit code if the child exited on its own.
49 signal <string> The signal by which the child process was terminated.
50  */
51 process.on('exit', function(code, signal) {
52     console.log(`master exit, code:${code}, signal:${signal}`);
53     for(let pid in workers) {
54         workers[pid].kill();
55     }
56 })
57 
58 process.on('uncaughtException', function(error) {
59     console.error('master | uncaughtException, error:', error);
60     process.exit(1);
61 })
62 
63 //一些經常使用的退出信號的處理:
64 // kill pid 默認是SIGTERM信號
65 // 控制檯 ctrl-c 是SIGINT信號
66 const killSignalList = ['SIGTERM', 'SIGINT'];
67 killSignalList.forEach((SIGNAL)=>{
68     process.on(SIGNAL, function(){
69         console.log(`${SIGNAL} signal`);
70         process.exit(1);
71     })  
72 })

 

 

master進程根據cpu核數fork相應數量的worker進程, fork成功後立刻把server句柄發送給worker進程, fork全部worker進程後, 就把server關掉,再也不接收請求。 master進程退出前會調用worker的kill()方法殺掉全部worker進程。架構設計

 

 

worker代碼以下:

 1 const http = require('http');
 2 
 3 
 4 const server = http.createServer(function(req, res) {
 5      // ‘request’ 監聽器
 6     res.end('handled by worker \n');
 7     // throw new Error('error');
 8 })
 9 
10 let worker;
11 process.on('message', function(msg, handle){
12     if(msg === 'server') {
13         worker = handle;
14         worker.on('connection', function(socket){
15             server.emit('connection', socket);
16         })
17     }
18 
19 })
20 
21 
22 process.on('uncaughtException', function(err) {
23     console.error('uncaughtException err:', err.message, ', worker進程將重啓');
24     // 通知master建立新的worker
25     process.send('new_worker', worker);
26     // 中止接收新的鏈接
27     worker.close(function() {
28         // 全部已有鏈接斷開後,退出進程
29         process.exit(1);
30     });
31 });

 

 

 worker進程有個細節處理的地方: 異常退出前,先通知master進程建立新的worker, 而後等待全部已有鏈接斷開後再退出進程。

 

關於進程間的句柄發送功能, 有興趣的同窗能夠再去了解一下, 子進程對象send(message,[sendHandle])方法能夠發送的句柄類型有:

  • net.Socket,  TCP套接字。
  • net.Server,  TCP服務器,任意創建在TCP服務上的應用層服務均可以享受到它帶來的好處。
  • net.Native, C++層面的TCP套接字或IPC通道。
  • dgram.Socket,  UDP套接字。
  • dgram.Native, C++層面的UDP套接字

 

多個worker進程監聽同一個套接字,會致使驚羣現象, 有請求過來時cpu會喚醒全部的worker進程, 最終只有一個進程accept到請求, 其它進程accept請求失敗,這種狀況會產生一些沒必要要的開銷。 如何避免驚羣現象,我另外寫一篇文章具體說一下。

相關文章
相關標籤/搜索