深入淺出nodejs學習筆記--第九章 玩轉程序管理
node的一個最大特性就是單執行緒,單執行緒帶來的好處是不用像多執行緒程式設計那樣去考慮狀態的同步問題,也不用去擔心出現死鎖,也沒有執行緒上下文所帶來的效能的開銷。但是同時也帶來了一些問題,比如無法充分利用的多核CPU,執行緒會阻塞的問題。
但是node真的就不能更高效了嗎,當然是不會的,如前幾篇筆記所說,node對於“多程序”的處理有自己的一套解決方案,今天就來簡單瞭解下。
服務模型的演變
在瞭解node的解決方法之前嗎,需要先了解一下Web伺服器關於客戶端請求的處理方案的一個演變過程,大概如下:
同步 --> 複製程序 --> 多執行緒 --> 事件驅動
一開始,是屬於同步的情況,同步的情況,一次只為一個請求服務。到後來,出現了改進,那就是複製程序,通過程序的複製可以服務更多的請求,但是這裡的問題是每一個請求都需要一個程序來服務,效能上比較浪費。再後來是多執行緒,多核CPU的出現,建立多個執行緒來處理請求,但是這個方案的問題是,在切換現成的同時也需要切換執行緒的上下文,當執行緒的數量過多,時間就會被耗費到上下文的切換上。最後的一個就是事件驅動的方案,node和 nginx都是基於事件驅動的方式實現的,採用單執行緒避免了不必要的記憶體開銷和上下文切換開銷。
node的多程序架構
node的多程序架構採用了child_process的方式,分為主程序和工作程序
如下演示如何建立一個子程序,以及一些操作
var child_process = require('child_process')
//啟動一個子程序
child_process.spawn('node', ['test.js'])
//啟動一個子程序,並執行命令
child_process.exec('node test.js', function (err, stdout, stderr) {
//回撥邏輯
})
//啟動一個子程序,並執行可執行檔案
child_process.execFile('test.js' , function (err, stdout, stderr) {
//回撥的邏輯
})
//與spawn類似,啟動一個子程序,不同的是它建立的程序只需要執行特定的檔案模組即可,不參與其他的
child_process.fork('./test.js')
程序之間的通訊
建立了子程序之後,主程序與子程序之間的通訊也是個大問題,程序間通訊的目的是為了讓不同的程序能夠互相訪問資源並進行協調工作。node是這樣的處理的。
node通過建立一個管道來解決。父程序在建立子程序之前,會建立管道(IPC通道)來監聽,然後才會建立子程序,並且此時子程序可以通過環境變數得到這個管道的檔案描述符。子程序在啟動的過程中,根據檔案描述符去連線這個已存在的IPC通道,從而完成父子程序之間的連線。當然在實際的通訊的過程中還會採用控制代碼傳遞的方式,說的簡單一點就是對一個資源的特殊標識,作用是可以實現多個子程序採用一個控制代碼來進行通訊。
充分利用多核CPU,node叢集
首先看一下叢集的概念,第一眼看到這個名詞的時候,有點蒙。
叢集:
在百度百科的解釋裡,叢集(cluster)技術是一種較新的技術,通過叢集技術,可以在付出較低成本的情況下獲得在效能、可靠性、靈活性方面的相對較高的收益,其任務排程則是集群系統中的核心技術。叢集的目的就是提高效能、降低成本、提高可擴充套件性、增強可靠性。
用我的理解就是,叢集就是指將很多伺服器集中起來,一起進行同一種服務,但是對客戶端來說,在服務端感覺就是一個的存在。
關於叢集,瞭解不深,只說兩個概念,負載均衡和狀態共享
- 負載均衡:伺服器也有負載均衡,但對於node所討論的,意思就是在多核CPU環境下,始終保證每個CPU都能被使用到從而保證最大效率。node採用的策略是輪叫排程,由主程序接手連線任務,然後依次分發給工作的子程序。
- 狀態共享: 也是有兩種情況,一種是要第三方儲存,利用Redis來實現,一種是主動通知,其實也需要通過輪詢來解決。
一個殺器,cluster模組
cluster是一個nodejs內建的模組,用於nodejs多核處理。有了這個東西,就基本不用上面的介紹的子程序child_process了。cluster模組可以幫助我們簡化多程序並行化程式的開發難度,輕鬆構建一個用於負載均衡的叢集。
下面看一下官方的示例:
var cluster = require('cluster')
var http = require('http')
var 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.id + 'died')
})
} else {
http.createServer(function(req, res) {
res.writeHead(200)
res.end('hello world')
}).listen(1234)
}
順便解釋一下cluster的工作原理:
cluster模組實際上是chlid_process和net模組的組合應用。cluster啟動時,會在內部啟動一個TCP伺服器,在cluster建立一個子程序(fork)時,將這個TCP伺服器端socket的檔案描述符傳送給工作程序。如果程序是複製出來的,並且存在網路埠的呼叫,那麼它就會拿到該檔案描述符,並重用,從而實現多個子程序共享埠。
前端新手,弱雞一枚,如有錯誤,請指正,謝謝!