此章節一共分爲兩個章節,下一節,nodejs-第二章-第三節-nodejs多進程-cluster(2-2)
內容索引
- 爲什麼要使用多進程
- 多進程和多線程介紹
- nodejs開啓多線程和多進程的方法
- cluster原理介紹
爲什麼要使用多進程
- nodjes單線程,在處理http請求的時候一個錯誤都會導致整個進程的退出,這是災難級的;
多進程和多線程介紹
- 進程是資源分配的最小單位,線程是CPU調度的最小單位
- 進程--資源分配最小單位,線程--程序諮詢最小單位
- 線程是進程的執行流,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位,一個進程由幾個線程組成,線程與同屬一個進程的其他線程共享進程所擁有的全部資源。
一個進程下面的線程是可以去通信的,共享資源
- 進程由獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑,線程有自己的堆棧和局部變量,但線程沒有嘟嘟的地址空間,一個線程死掉等於整個進程死掉;
- 谷歌瀏覽器
- 進程:一個tab就是一個進程
- 線程:一個tab又由多個線程組成,渲染線程,js執行線程,垃圾回收,service worker等
- node服務
- ab是Apache自帶的壓力測試工具
- ab -1000 -c20 '192.168.31.25:8000/'
- 進程:監聽某個端口的http服務
- 線程:http服務由多個線程組成,比如:
- 主線程:獲取代碼,編譯執行
- 編譯線程:主線程執行的時候,可以優化代碼(v8引擎)
- Profiler線程:記錄哪些方法耗時,爲優化提供支持
- 其他線程:用於垃圾回收清除工作,因爲多個線程,所以可以並行清除
多線程和多線程的選擇
多進程和多線程一般可以結合起來使用
- 多進程:穩定,安全
- 多線程:快
對比維度 | 多進程 | 多線程 | 總結 |
---|---|---|---|
數據共享、同步 | 數據共享複雜,需要用IPC;數據是分開的,同步簡單 | 因爲共享進程數據,數據共享簡單,但也是因爲這個導致同步複雜 | 各有優勢 |
內存、cpu | 佔用內存多,切換負責,cpu利用率低 | 佔用內存少,切換簡單,cpu利用率高 | 線程佔優 |
創建銷燬、切換 | 創建銷燬複雜,速度慢 | 創建銷燬簡單,速度很快 | 線程佔優 |
編程、調試 | 編程簡單,調試簡單 | 編程複雜、調試複雜 | 進程佔優 |
可靠性 | 進程間不會相互影響 | 一個線程掛掉將導致整個進程掛掉 | 進程佔優 |
分佈式 | 適應於多核、多機分佈式;如果一臺機器不夠,擴展到多臺機器比較簡單 | 適應於多核分佈式 | 進程佔優 |
- 1.需要頻繁發創建銷燬的優先使用線程
- 常見Web服務器,來一個連接創建一個線程,斷了就銷燬線程,要是用進程,創建和銷燬的代價都是難以承受的
- 2.需要進行大量計算的優先使用線程
- 所謂大量計算,就是要耗費很多cpu,切換頻繁了,這種情況下線程是適合的;常見:圖像處理,算法處理;
- 強相關的用線程處理,弱相關的用進程處理
- 如:消息收發,消息處理;
- 消息收發和消息處理屬於若相關任務,分進程設計
- 消息處理裏面可能又分消息解碼、業務處理,關聯性強,分線程設計;
- 可能要擴展到多機器分佈用進程,多核分佈用線程
- 都可滿足的情況,用擅長的
nodejs多線程
- worker_threads模塊
創建多進程
利用cluster開啓多進程
- ab是apache自帶的壓力測試工具
- ab -n1000 -c20 '192.168.31.25:8000/'
const cluster = require('cluster'); // 多進程
const http = require('http');
const numCpus = require('os').cpus().length; // 獲取cpu的核數
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((req,res) =>{
res.writeHead(200);
res.end('hello world');
}).listen(8000)
}
多進程和單進程性能對比
多進程的性能要明顯好於單進程
- ab是apache自帶的壓力測試工具,推薦使用mac
- ab -n1000 -c20 '192.168.31.25:8000/'
- n 請求數量
- c 併發數量
nodejs調試工具
- vscode的.vscode下面配置 launch.json; 調試 修改program 工作目錄
cluster 相關API
Process進程,child_process子進程,Cluster集羣
process 進程
process對象是Node的一個全局對象,提供當前Node進程的信息,它也可以在腳本的任意位置使用,不必通過require獲取;
屬性
- process.argv屬性,返回一個數組,包含了node進程時的命令行參數;
- process.env返回包含用戶環境信息的對象,可以在腳本中心對這個隊形進行curd操作;
- process.pid返回當前進程的進程號
- process.platform 返回當前的操作系統
- process.version 返回當前node的版本
方法
- process.cwd() 返回node.js進程當前的工作目錄
- proces.chdir() 變更node.js進程的工作目錄
- process.nextTick() 將任務放到當前時間循環的尾部,添加到next tick隊列,一旦當前時間輪詢隊列的任我全部完成,在next tick隊列的所有callback會被依次調用;
- process.exit() 退出當前進程時觸發;很多時候是不需要的;
- process.kill(pid[,signal]) 給指定的進程發信號,包括但不限於結束進程;
事件
- beforeExit事件,在node清空了EventLoop之後,再沒有任何待處理任務時觸發,可以在這裏再部署一些任務,使得node進程不退出,顯示的終止程序時(process.exit()),不會觸發;
- exit事件,當前進程退出時觸發,回調函數中只允許同步操作,因爲執行完回調後,進程全部退出;
- uncaughtException事件,兜底方案,當前進程拋出一個沒有捕獲的異步錯誤時觸發,可以用它在進程結束前進行一些已分配資源的同步清理操作,嘗試用它來恢復應用的正常運行的操作是不安全的;
- warning事件;任何nodejs發出的警告都會觸發此事件;
bbb()
// 這裏會直接報錯,因爲js是單線程的,
// uncaughtException專門是捕捉異步代碼錯誤,特別是http
process.on('uncaughtException', (err) =>{
console.log(err)
})
const http = require('http');
http.createServer((req, res) => {
ccc()
}).listen(8000, () => {
console.log(`server is runing on 8000`)
})
process.on('uncaghtException', (err) => {
// 這裏會捕獲ccc的異步錯誤s
console.log('發生錯誤',err)
})
child_process
node中用於創建子進程的模塊,cluster就是基於child_process模塊封裝的;
- child_process.exec()
- 執行異步命令,運行結束後調用回調函數,或監聽事件輸出;
- 參數可以隨便輸入,安全性不高
- 方法2在監聽到data事件以後, 可以一邊讀取一邊接收結果,不用等子進程結束;如果子進程運行時間較長,或者持續運行,建議使用方法2;
const exec = require('child_process').exec;
// 1. 通過回調的方式接收結果
// exec('ls', (err, stdout, stderr) => {
// // 在node中,容錯處理和業務代碼一樣重要;
// // 因爲js是單線程,一旦發生錯誤,後面代碼就不會執行
// if (err) {
// console.log('stderr', stderr);
// }
// console.log('err', err);
// console.log('stdout', stdout);
// });
// 由於標準輸出和標準錯誤都是流對象(stream),可以監聽data事件
// 2. 通過流的方式返回結果,
// 可以一邊讀取一邊接收結果,不用等所有文件讀取完
var child = exec('lss');
child.stdout.on('data', data => {
console.log(data);
});
// 發生錯誤
child.stderr.on('data', err => {
console.log('發生錯誤了', err);
});
console.log(111)
- child_progress.execSync() 同步方法
var execSync('child_progress');
var path = '../'
var child = execSync(`ls ${path} \ rm rf`); // 這樣會刪除此文件夾的上一級目錄的所有文件
console.log(child.toString());
- execFile()
- 直接執行特定的程序shell,參數作爲數組傳入,不會被bash解釋,因此具有較高的安全性;
- 會自動過濾一些敏感的字符串,如:'\ ;'
const { execFile } = require('child_progress')
execFile('ls', ['-c'], (err, stdout, stderr) => {
console.log('stdout', stdout)
})
- child_process.spawn()
- spawn 創建一個子進程來執行特定的shell,用法和execFile 類似,但是沒有回調函數,只能通過監聽事件來獲取運行結果,它屬於異步執行,適用於子進程長時間運行的情況;
- spawn 返回的結果是buffer,需要轉成utf8;
const { spawn } = require('child_process');
let child = spawn('ls', ['-c']);
child.stdout.on('data', data => {
console.log('data', data.toString('utf8'));
});
- child_process.fork()
- child_process.fork() 會創建一個子進程,執行node腳本,
- child_process.fork('./child.js') 相當於 child_process.spawn('node', ['./child.js']);
- 與child_process.spawn()方法不同的是,child_process.fork()方法會在父進程和子進程之間建立一個通信管道pipe,用於進程之間的通信,也是IPC通信的基礎;
main.js
const child_process = require('child_process');
const path = require('path');
var child = child_process.fork(path.resolve(__dirname, './child.js'));
child.on('message', data => {
console.log('父接收到子消息:', data);
});
child.send('父親send', data => {
console.log('父親說:爲父給你發消息了');
});
child.js
process.on('message', data => {
console.log('兒子接收到父親消息:', data);
});
process.send('兒子send', data => {
console.log('兒子對父親說:hello ');
});