cluster通過好幾代的發展,如今已經比較好使了。利用cluster,能夠自動完成子進程worker分配request的事情,就再也不須要本身寫代碼在master進程中robin式給每一個worker分配任務了。node
const cluster = require('cluster'); const http = require('http'); const numCPUs = require('os').cpus().length; if (cluster.isMaster) { // Fork workers. for (var i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', (worker, code, signal) => { console.log(`worker ${worker.process.pid} died`); }); } else { // Workers can share any TCP connection // In this case it is an HTTP server http.createServer((req, res) => { res.writeHead(200); res.end('hello world\n'); }).listen(80); }
上述簡單的代碼,就實現了根據CPU個數,建立多個worker。至於實際項目中,是正好一個核對應一個worker呢,仍是一個核對應二、3個worker呢,就視狀況而定了。若是項目中,等待其餘服務器(例如數據庫)響應特別長時間,設置2個以上worker應該會更好。linux
不過通常而言,一個CPU對一個worker就挺好的了。shell
那麼,整個架構就相似這樣:數據庫
Master進程,須要作的就是監控worker的生命週期,若是發現worker掛掉了,就重啓worker,並作好相應的log。服務器
整個架構沒有太大的難點,重點就是作好一些細節處理,例如重啓、日誌、5秒心跳包等。架構
多進程的架構,相對原始的單進程+pm2重啓好處確定多不少,整個node服務會更穩定,不會忽然完全掛了。負載均衡
另外,對比pm2多進程,也有優點,主要是master的邏輯掌握在開發本身手中,能夠作好自定義的log和郵件、短信告警。curl
爲了整個nodejs服務管理方便,在master進程中,咱們通常開啓管理端口的監聽,例如12701,經過命令行curl 127.0.0.1:12701:xxx發起一個簡單的http get請求,輕鬆管理。ui
例如xxx傳入reload,能夠做爲服務器重啓的指令。this
說到多進程,目的確定是儘量利用多核CPU,提升單機的負載能力。
但每每在實際項目中,受到業務邏輯的處理時間長短和系統CPU調度影響,致使實際上全部進程的負載並非理想的完全均衡。
官方也說了:
In practice however, distribution tends to be very unbalanced due to operating system scheduler vagaries. Loads have been observed where over 70% of all connections ended up in just two processes, out of a total of eight.
翻譯一下:70%的請求最終都落到2個worker身上,而這2個worker佔用更多的CPU資源。
那麼在實際項目部署,咱們能夠嘗試更進一步的措施:綁定CPU。4核CPU,咱們fork出4個worker,每一個worker分別綁定到#1-#4 CPU。
node並無給咱們提供現成的接口,不過咱們可使用linux的命令:taskset
在node中,咱們可使用child_process執行shell。
cp.exec('taskset -cp ' + (cpu) + ' ' + process.pid,{ timeout: 5000 },function(err,data,errData){ if(err){ logger.error(err.stack); } if(data.length){ logger.info('\n' + data.toString('UTF-8')); } if(errData.length){ logger.error('\n' + errData.toString('UTF-8')); } });
按實際狀況來看,效果是不錯的。
每次發佈新版本,服務器必然須要重啓。
簡單粗暴的,殺掉主進程,所有重啓,必然會有一段時間的服務中斷。
對於小企業還好,能夠安排在凌晨重啓,但對於大公司大產品來講,就不能這麼粗暴了。
那麼咱們須要平滑重啓,實現重啓過程當中,服務不中斷。
策略並不複雜,但很是有效:
一、worker進程輪流重啓,間隔時間;
二、worker進程並非直接重啓,而是先關閉新請求監聽,等當前請求都返回了,再重啓。
try { // make sure we close down within 30 seconds var killtimer = setTimeout(() => { process.exit(1); }, 30000); // stop taking new requests. server.close(); // Let the master know we're dead. This will trigger a // 'disconnect' in the cluster master, and then it will fork // a new worker. cluster.worker.disconnect(); } catch (er2) { }
實施了平滑重啓後,服務器的吞吐率會平滑不少。