深入淺出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的文件描述符發送給工作進程。如果進程是複製出來的,並且存在網絡端口的調用,那麼它就會拿到該文件描述符,並重用,從而實現多個子進程共享端口。

有關cluster詳細的代碼實踐,可以參考這篇博客:https://cnodejs.org/topic/56e84480833b7c8a0492e20c


前端新手,弱雞一枚,如有錯誤,請指正,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章