新聞中心
相信對于前端同學(xué)而言,我們?nèi)ラ_發(fā)一個自己的簡單后端程序可以借助很多的??nodeJs??的框架去進行快速搭建,但是從前端面向后端之后,我們會在很多方面會稍顯的有些陌生,比如性能分析,性能測試,內(nèi)存管理,內(nèi)存查看,使用C++插件,子進程,多線程,Cluster模塊,進程守護管理等等??NodeJs??后端的知識,在這里為大家來分析一下這些場景與具體實現(xiàn)。

創(chuàng)新互聯(lián)建站網(wǎng)站建設(shè)提供從項目策劃、軟件開發(fā),軟件安全維護、網(wǎng)站優(yōu)化(SEO)、網(wǎng)站分析、效果評估等整套的建站服務(wù),主營業(yè)務(wù)為成都網(wǎng)站制作、成都網(wǎng)站設(shè)計,成都app軟件開發(fā)以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。創(chuàng)新互聯(lián)建站深信只要達到每一位用戶的要求,就會得到認可,從而選擇與我們長期合作。這樣,我們也可以走得更遠!
搭建基礎(chǔ)服務(wù)
首先我們先來實現(xiàn)一個簡單的??Http服務(wù)器??,為了演示方便這里我們使用??express??,代碼如下:
const fs = require('fs')
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.end('hello world')
})
app.get('/index', (req, res) => {
const file = fs.readFileSync(__dirname + '/index.html', 'utf-8')
/* return buffer */
res.end(file)
/* return stream */
// fs.createReadStream(__dirname + '/index.html').pipe(res)
})
app.listen(3000)正常情況我們大部分的后端服務(wù)是聯(lián)合??db??最終返回一些列的接口信息的,但是為了后面的一些測試,這里我們返回了一個文件,因為大一點的返回信息可以直觀的感受我們的服務(wù)性能與瓶頸。
額外一點,在上面可以看到我們在注釋的地方也使用了一個??stream流??的形式進行了返回,如果我們返回的是文件,第一種的同步讀取其實相對更耗時,如果是個大的文件,會在內(nèi)存空間先去存儲,拿到全部的文件之后才會一次返回,這樣的性能包括內(nèi)存占用在文件較大的時候更為明顯。
所以如果我們做的是??ssr??或者文件下載之類的東西我們都可以以這樣流的形式去做更加高效,至此,我們已經(jīng)有了一個簡單的??http服務(wù)??了,接下來我們對齊進行擴展。
性能測試、壓測
首先我們需要借助測試工具模擬在高并發(fā)情況下的狀態(tài),這里我推薦兩種壓測工具。
- ab 官方文檔
- webbench
- autocannon
本次我們使用??ab??壓測工具來進行接下來的操作,所以這里為大家介紹一下??ab??。那么??ab??呢是??apache??公司的一款工具,??mac??系統(tǒng)是自帶這個工具的,安裝教程呢大家就自行去查看,當然??mac??自帶的??ab??是有并發(fā)限制的。
然后我們先隨便來一條簡單的命令再為大家分析一下具體的參數(shù)
ab -c200 -n1600 http://127.0.0.1:3000/index
上面這條命令的意思呢就是測試接口地址??http://127.0.0.1:3000/index??對齊每秒200個請求,并請求總數(shù)1600次這樣的一個壓測,然后我們看看這個工具的其他參數(shù)吧
|
參數(shù) |
解釋 |
|
? |
設(shè)定并發(fā)數(shù),默認并發(fā)數(shù)是 1 |
|
? |
設(shè)定壓測的請求總數(shù) |
|
? |
設(shè)定壓測的時長,單位是秒 |
|
? |
設(shè)定 POST 文件路徑,注意設(shè)定匹配的 ? |
|
? |
設(shè)定 ? |
|
? |
查看版本信息 |
|
? |
以Html表格形式輸出 |
參數(shù)并不多很簡單,當然我們需要看看壓測之后的結(jié)果,這才是我們需要的東西。
上面的東西呢其實已經(jīng)很直觀了,最開頭的部分就是每秒請求成功了多少個,其次就是請求地址、端口、路徑、大小、這些其實不是很重要,我們在瀏覽器中自己也可以看到,我們主要需要注意的性能指標是下面這些參數(shù):
Complete requests: 1600 # 請求完成成功數(shù) 這里判斷的依據(jù)是返回碼為200代表成功
Failed requests: 0 # 請求完成失敗數(shù)
Total transferred: 8142400 bytes # 本次測試傳輸?shù)目倲?shù)據(jù)
HTML transferred: 7985600 bytes
Requests per second: 2188.47 [#/sec] (mean) # QPS 每秒能夠處理的并發(fā)量
Time per request: 91.388 [ms] (mean) # 每次請求花費的平均時常
Time per request: 0.457 [ms] # 多久一個并發(fā)可以得到結(jié)果
Transfer rate: 10876.09 [Kbytes/sec] received # 吞吐量 每秒服務(wù)器可以接受多少數(shù)據(jù)傳輸量
一般而言我們只需要注意最后四條即可,首先可以直觀知道當前服務(wù)器能承受的并發(fā),同時我們可以知道服務(wù)器的瓶頸來自于哪里,如何分析呢?如果這里的吞吐量剛好是我們服務(wù)器的網(wǎng)卡帶寬一樣高,說明瓶頸來自于我們的帶寬,而不是來自于其他例如cpu,內(nèi)存,硬盤等等,那么我們其他的如何查看呢,我們可以借助這兩個命令:
- top 監(jiān)控計算機cpu和內(nèi)存使用情況
- iostat 檢測io設(shè)備的帶寬的
我們就可以在使用??ab壓測??的過程中實時查看服務(wù)器的狀態(tài),看看瓶頸來自于cpu、內(nèi)存、帶寬等等對癥下藥。
當然存在一種特殊情況,很多場景下NodeJs只是作為BFF這個時候假如我們的Node層能處理600的qps但是后端只支持300,那么這個時候的瓶頸來自于后端。
在某些情況下,負載滿了可能也會是NodeJs的計算性能達到了瓶頸,可能是某一處的代碼所導(dǎo)致的,我們?nèi)绾稳フ业?strong>NodeJs的性能瓶頸呢,這一點我們接下來說說。
Nodejs性能分析工具
profile
NodeJs自帶了profile工具,如何使用呢,就是在啟動的時候加上**--prof**即可,??node --prof index.js??,當我們啟動服務(wù)器的時候,目錄下會立馬生成一個文件??isolate-0x104a0a000-25750-v8.log??,我們先不用關(guān)注這個文件,我們重新進行一次15秒的壓測:
ab -c50 -t15 http://127.0.0.1:3000/index
等待壓測結(jié)束后,我們的這個文件就發(fā)生了變化,但是里面的數(shù)據(jù)很長我們還需要進行解析。
使用NodeJs自帶的命令 ??node --prof-process isolate-0x104a0a000-25750-v8.log > profile.txt??
這個命令呢就是把我們生成的日志文件轉(zhuǎn)為txt格式存在當前目錄下,并且更為直觀可以看到,但是這種文字類型的對我來說也不是足夠方便,我們大致說說里面的內(nèi)容吧,就不上圖了,里面包含了,里面有js,c++,gc等等的各種調(diào)用次數(shù),占用時間,還有各種的調(diào)用棧信息等等,這里你可以手動實現(xiàn)之后看看。
總體來說還是不方便查看,所以我們采用另一種方式。
chrome devtools
因為我們知道NodeJs是基礎(chǔ)chrome v8引擎的javascript運行環(huán)境,所以我們調(diào)試NodeJs也是可以對NodeJs進行調(diào)試的。這里我們要使用新的參數(shù)??--inspect??, ??-brk??代表啟動調(diào)試的同時暫停程序運行,只有我們進入的時候才往下走。
??node --inspect-brk index.js??
(base) xiaojiu@192 node-share % node --inspect-brk index.js
Debugger listening on ws://127.0.0.1:9229/e9f0d9b5-cdfd-45f1-9d0e-d77dfbf6e765
For help, see: https://nodejs.org/en/docs/inspector
運行之后我們看到他就告訴我們監(jiān)聽了一個??websocket??,我們就可以通過這個ws進行調(diào)試了。
我們進入到chrome瀏覽器然后在地址欄輸入??chrome://inspect??
然后我們可以看到other中有一個Target,上面輸出了版本,我們只需要點擊最后一行的那個inspect就可以進入調(diào)試了。進入之后我們發(fā)現(xiàn),上面就可以完完整整看到我們寫的源代碼了。
并且我們進入的時候已經(jīng)是暫停狀態(tài)了,需要我們手動下去,這里和前端調(diào)試都大同小異了,相信這里大家都不陌生了。
除此之外,我們可以看到其他幾個面板,Console:控制臺、Memory:內(nèi)存監(jiān)控、Profile:CPU監(jiān)控,
CPU監(jiān)控
我們可以進入到Memory面板,點擊左上角的原點表示開始監(jiān)控,這個時候進行一輪例如上面的15s壓測,壓測結(jié)束后我們點擊stop按鈕,這個時候就可以生成這個時間段的詳細數(shù)據(jù)了,結(jié)果如下:
我們也可點擊??hHeavy按鈕??切換這個數(shù)據(jù)展現(xiàn)形式為圖表等其他方式,大家自己試試,那么從這個數(shù)據(jù)中,我們可以得到什么呢?在這其中記錄了所有的調(diào)用棧,調(diào)用時間,耗時等等,我們可以詳細的知道,我們代碼中每一行或者每一步的花費時間,這樣再對代碼優(yōu)化的話是完全有跡可循的,同時我們使用圖表的形式也可以更為直觀的查看的,當然這里不僅僅可以調(diào)試本地的,也可以通過服務(wù)器ip在設(shè)置中去調(diào)試遠端服務(wù)器的,當然可能速度會相對慢一點,可以自己去嘗試。同時我們也可以借助一些其他的三方包,比如clinic,有興趣的各位可以自己去查看一下。
我們看他的意義是什么呢,當然是分析各個動作的耗時然后對齊進行代碼優(yōu)化了,接下來怎么優(yōu)化呢?
代碼性能優(yōu)化
通過上面的分析,我們可以看到花費時間最長的是readFileSync,很明顯是讀取代碼,那么我們對最最初的代碼進行分析,可以看到當我們每次訪問/indexd路徑的時候都會去重新讀取文件,那么很明顯這一步就是我們優(yōu)化的點,我們稍加改造:
const fs = require('fs')
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.end('hello world')
})
/* 提取到外部每次程序只會讀取一次 提高性能 */
const file = fs.readFileSync(__dirname + '/index.html', 'utf-8')
app.get('/index', (req, res) => {
/* return buffer */
res.end(file)
/* return stream */
// fs.createReadStream(__dirname + '/index.html').pipe(res)
})
app.listen(3000)為了直觀感受,我們在改造前后分別壓測一次看看,這里呢就不上圖了,大家可以自己動手,會發(fā)現(xiàn)這樣的操作可以讓你的qps可以直接翻倍,可以看到,這樣分析處出來的結(jié)果,再對代碼改造可以提高非常大的效率。
同時除此之外,還有一個地方可以優(yōu)化,我們發(fā)現(xiàn)上圖我點開的箭頭部分有一個byteLengthUtf8這樣的一個步驟,可以看出他是獲取我們文件的一個長度,因為我們指定了上方的獲取格式是utf-8,那么我們想想獲取長度是為了什么呢?因為NodeJs的底層是基于C++,最終識別的數(shù)據(jù)結(jié)構(gòu)還是buffer,所以思路就來了,我們直接為其傳遞一個buffer是不是就更快了呢?
事實確實如此,readFileSync不指定格式的時候默認就是Buffer,當我們?nèi)サ糁付愋偷臅r候,再去壓測,發(fā)現(xiàn)qps再次增加了,所以在這里我們明白,在很多操作中使用buffer的形式可以提高代碼的效率與性能。
當然還有許多其他的點,那些地方的優(yōu)化可能就不太容易了,但是我們只需要去處理這些占用大頭的點就已經(jīng)足夠了,我們只需要知道去優(yōu)化的手段與思路,剛剛這個的優(yōu)化就是把一些需要計算啊或者讀取這種需要時間的操作移動到服務(wù)啟動之前去完成就可以做到一個比較好的性能思想,那么我們性能優(yōu)化需要考慮哪些點呢?
性能優(yōu)化的準則
- 減少不必要的計算:NodeJs中計算會占用相當大的一部分cpu,包括一些文件的編解碼等等,盡量要避免這些操作。
- 空間換時間:比如上面這種讀取,或者一些計算,我們可以緩存起來,下次讀取的時候直接調(diào)用。
掌握這兩點,我們在編碼過程中要盡量思考某些計算是否可以提前,盡量做到在服務(wù)啟動階段去進行處理,把在服務(wù)階段的計算提前到啟動階段就可以做到不錯的提升效果。
內(nèi)存管理
垃圾回收機制
我們都知道javascript的內(nèi)存管理都是由語言自己來做,不需要開發(fā)者來做,我們也知道其是通過GC垃圾回收機制實現(xiàn)的,我們粗略聊一下,一般來說呢,垃圾回收機制分為,新生代和老生代兩部分,所有新創(chuàng)建的變量都會先進入新生代部分,當新生代內(nèi)存區(qū)域快要分配滿的時候,就會進行一次垃圾回收,把無用的變量清楚出去給新的變量使用,同時,如果一個變量在多次垃圾回收之后依然存在,那么則認為其是一個常用且不會輕易移除的變量,就會將其放入老生代區(qū)域,這樣一個循環(huán),同時,老生代區(qū)域容量更大,垃圾回收相對更慢一些。
- 新生代:容量小、垃圾回收更快
- 老生代:容量大,垃圾回收更慢
所以減少內(nèi)存的使用也是提高服務(wù)性能的手段之一,如果有內(nèi)存泄漏,會導(dǎo)致服務(wù)器性能大大降低。
內(nèi)存泄漏問題處理與修復(fù)
剛剛我們上面介紹過Memory面板,可以檢測,如何使用呢,點擊面板之后點擊右上角遠點會產(chǎn)生一個快照,顯示當前使用了多少內(nèi)存空間,正常狀態(tài)呢,我就不為大家演示了,一般如何檢測呢,就是在服務(wù)啟動時截取一個快照,在壓測結(jié)束后再截取一個看看雙方差異,你也可以在壓測的過程中截取快照查看,我們先去修改一些代碼制造一個內(nèi)存泄漏的現(xiàn)場,改動如下:
const fs = require('fs')
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.end('hello world')
})
const cache = []
/* 提取到外部每次程序只會讀取一次 提高性能 */
const file = fs.readFileSync(__dirname + '/index.html', 'utf-8')
app.get('/index', (req, res) => {
/* return buffer */
cache.push(file)
res.end(file)
/* return stream */
// fs.createReadStream(__dirname + '/index.html').pipe(res)
})
app.listen(3000)我們每次請求都把讀取的這個文件添加到cache數(shù)組,那么意味著請求越多,這個數(shù)組將會越大,我們和之前一樣 ,先打開調(diào)試,同時截取一份快照,然后開始壓測,壓測結(jié)束再截圖一份,也可以在壓測過程中多次截圖,得到如下:
我們在壓測過程中不斷截取快照發(fā)現(xiàn)內(nèi)存一直在加大,這就是很直觀的可以看到內(nèi)存泄漏,而且因為我們的文件不大,如果是一個更大的文件,會看起來差異更懸殊,然后我們點擊Comparsion按鈕位置,選擇完快照之后進行比較,然后點擊占用最大的那一列,點擊之后我們就能看到詳細信息了,此次泄漏就是cache變量所導(dǎo)致的,對齊進行修復(fù)即可,在我們知道如何修復(fù)和檢測內(nèi)存泄漏之后,我們就應(yīng)該明白,減少內(nèi)存的使用是提高性能的一大助力,那么我們?nèi)绾螠p少內(nèi)存的使用呢?
控制內(nèi)存使用
在此之前我們聊聊NodeJs的Buffer的內(nèi)存分配策略,他會分為兩種情況,一種是小于8kb的文件,一種是大于8kb的文件,小于8kb的文件NodeJs認為頻繁的去創(chuàng)建沒有必要,所以每次都會先創(chuàng)建一個8kb的空間,然后得到空間之后的去計算buffer的占用空間,如果小于8kb就在8kb中給它切一部分使用,依次內(nèi)推,如果遇到一個小于8kb的buffer使余下的空間不夠使用的時候就會去開辟新的一份8kb空間,在這期間,如何有任何變量被銷毀,則這個空間就會被釋放,讓后面的使用,這就是NodeJs中Buffer的空間分配機制,這種算法類似于一種池的概覽。如果在我們的編碼中也會遇到內(nèi)存緊張的問題,那么我們也可以采取這種策略。
至此我們對于內(nèi)存監(jiān)控已經(jīng)查找已經(jīng)學(xué)會了,接下來我們來看看多進程如何使用與優(yōu)化。
Node多進程使用優(yōu)化
現(xiàn)在的計算機一般呢都搭載了多核的cpu,所以我們在編程的時候可以考慮怎么去使用多進程或者多線程來盡量利用這些多核cpu來提高我們的性能。
在此之前,我們要先了解一下進程和線程的概覽:
- 進程:擁有系統(tǒng)掛載運行程序的單元 擁有一些獨立的資源,比如內(nèi)存空間
- 線程:進行運算調(diào)度的單元 進程內(nèi)的線程共享進程內(nèi)的資源 一個進程是可以擁有多個線程的
在NodeJs中一般啟動一個服務(wù)會有一個主線程和四個子線程,我們簡單來理解其概覽呢,可以把進程當做一個公司,線程當做公司的職工,職工共享公司的資源來進行工作。
在NodeJs中,主線程運行v8與javascript,主線程相當于公司老板負責(zé)主要流程和下發(fā)各種工作,通過時間循環(huán)機制、LibUv再由四個子線程去進行工作。
因為js是一門單線程的語言,它正常情況下只能使用到一個cpu,不過其子線程在 底層也使用到了其他cpu,但是依然沒有完全解放多核的能力,當計算任務(wù)過于繁重的時候,我們就可以也在其他的cpu上跑一個javascript的運行環(huán)境,那么我么先來看看如何用子進程來調(diào)用吧。
進程的使用 child_process
我們創(chuàng)建兩個文件,??master.js??和??child.js??,并且寫入如下代碼,
/* master.js */
/* 自帶的子進程模塊 */
const cp = require('child_process')
/* fork一個地址就是啟動了一個子進程 */
const child_process = cp.fork(__dirname + '/child.js')
/* 通過send方法給子進程發(fā)送消息 */
child_process.send('主進程發(fā)這個消息給子進程')
/* 通過 on message響應(yīng)接收到子進程的消息 */
child_process.on('message', (str) => {
console.log('主進程: 接收到來自自進程的消息', str);
})
/* chlid.js */
/* 通過on message 響應(yīng)父進程傳遞的消息 */
process.on('message', (str) => {
console.log('子進程, 收到消息', str)
/* process是全局變量 通過send發(fā)送給父進程 */
process.send('子進程發(fā)給主進程的消息')
})
如上,就是一個使用子進程的簡單實現(xiàn)了,看起來和ws很像。每fork一次便可以開啟一個子進程,我們可以fork多次,fork多少個合適呢,我們后邊再說。
子線程 WOKer Threads
在v10版本之后,NodeJs也提供了子線程的能力,在官方文檔中解釋到,官方認為自己的事件循環(huán)機制已經(jīng)做的夠好足夠使用了,就沒必要去為開發(fā)者提供這個接口,并且在文檔中寫到,他可以對計算有所幫助,但是對io操作是沒有任何變化的,有興趣可以去看看這個模塊,除此之外,我們可以有更簡單的方式去使用多核的服務(wù),接下來我們聊聊內(nèi)置模塊cluster
Cluster模塊
在此之前我們來聊聊NodeJs的部署,熟悉NodeJs的同學(xué)應(yīng)該都使用過Pm2,利用其可以進程提高不熟的性能,其實現(xiàn)原理就是基于這種模塊,如果我們可以在不同的核分別去跑一個http服務(wù)那么是不是類似于我們后端的集群,部署多套服務(wù)呢,當客戶端發(fā)送一個Http請求的時候進入到我們的master node,當我們收到請求的時候,我們把其請求發(fā)送給子進程,讓子進程自己處理完之后返回給我,由主進程將其發(fā)送回去,那么這樣我們是不是就可以利用服務(wù)器的多核呢?
答案是肯定的,同時這些都不需要我們做過多的東西,這個模塊就幫我們實現(xiàn)了,然后我們來實現(xiàn)一個這樣的服務(wù),我們創(chuàng)建兩個文件??app.js??,??cluster.js??,第一個文件呢就是我們?nèi)粘5膯游募?,我們來簡單的,使用我們的最開始的那個服務(wù)即可:
/* cluster.js */
const cluster = require('cluster')
/* 判斷如果是主線程那么就啟動三個子線程 */
if(cluster.isMaster){
cluster.fork()
cluster.fork()
cluster.fork()
} else {
/* 如果是子進程就去加載啟動文件 */
require('./index.js')
}
就這樣簡單的代碼就可以讓我們的請求分發(fā)到不同的子進程里面去,這一點類似于負載均衡,非常簡單,同時我們在啟用多線程和沒啟動的前后分別壓測,可以發(fā)現(xiàn)啟用后的qps是前者的2.5倍擁有很大的一個提升了,也可以知道進程直接的通信是有損耗的,不然應(yīng)該就是三倍了,那么我們要開啟多少個子進程比較合適呢。我們可以使用內(nèi)置模塊OS,來獲取到當前計算機的cpu核數(shù)的,我們加一點簡單改造:
const cluster = require('cluster')
const os = require('os')
/* 判斷如果是主線程那么就啟動三個子線程 */
if(cluster.isMaster){
/* 多少個cpu啟動多少個子進程 */
for (let i = 0; i < os.cpus().length; i++) cluster.fork()
} else {
/* 如果是子進程就去加載啟動文件 */
require('./index.js')
}這樣我們就可以準確知道計算機有多少個cpu我們最多可以啟動多少個子進程了,這時我們進行壓測發(fā)現(xiàn)qps更多了,當然并不是啟動的越多就越好,前面我們說到。NodeJs的底層是用到了其他cpu的所以,我們這里一般來說只需要os.cpus().length / 2的數(shù)量最為合適,就這么簡單我們就使用到了其他cpu實現(xiàn)了一個類似負載均衡概念的服務(wù)。
當然這里有一個疑問,我們手動啟動多次node app.js為什么不行呢?很明顯會報錯端口占用,我們知道,正常情況下計算機的一個端口只能被監(jiān)聽一次,我們這里監(jiān)聽了多次實際就是有NodeJs在其底層完成的,這里的實現(xiàn)呢就相對復(fù)雜需要看源碼了,這里就不過多了解了,有興趣的同學(xué)可以自己去研究一下。
如果你做完這些操作,相信你的服務(wù)性能已經(jīng)提高了很大一截了。接下來我們來聊聊關(guān)于其穩(wěn)定性的安全。
NodeJs進程守護與管理
基本上各種NodeJs框架都會有全局捕獲錯誤,但是一般自己去編碼的過程中沒有去做try catch的操作就可能導(dǎo)致你的服務(wù)直接因為一個小錯誤直接掛掉,為了提高其穩(wěn)定性,我們要去實現(xiàn)一個守護,我們用原生的node來創(chuàng)建一個服務(wù),不做異常處理的情況下,如果是框架可能很多框架已經(jīng)幫你做過這部分東西了,所以我們自己來實現(xiàn)看看吧:
const fs = require('fs')
const http = require('http')
const app = http.createServer( function(req,res) {
res.writeHead(200, { 'content-type': 'text/html'})
console.log(window.xxx)
res.end(fs.readFileSync(__dirname + './index.html', 'utf-8'))
} )
app.listen(3000, () => {
console.log(`listen in 3000`);
})我們在請求時去打印一個不存在的變量,我們?nèi)フ埱蟮脑捑蜁M行一個報錯,同時進程直接退出,而我們?nèi)绻褂枚嗑€程啟動的話,也會在我們請求多線程的個數(shù)之后,主線程退出,因為主線程發(fā)現(xiàn)所有子線程全都掛掉了就會退出,基于這種文件我們希望不要發(fā)生,我們怎么做可以解決呢,
內(nèi)置了一個事件uncaughtException可以用來捕獲錯誤,但是管方建議不要在這里組織塔退出程序,但是我們可以在退出程序前對其進行錯誤上報,我們對cluster.js進行輕微改造即可,同時我們也可以通過cluster模塊監(jiān)控,如果有的時候發(fā)生錯誤導(dǎo)致現(xiàn)線程退出了,我們也可以進行重啟,那么改造如下:
const cluster = require('cluster')
const os = require('os')
/* 判斷如果是主線程那么就啟動三個子線程 */
if(cluster.isMaster){
/* 多少個cpu啟動多少個子進程 */
for (let i = 0; i < os.cpus().length; i++) cluster.fork()
/* 如果有線程退出了,我們重啟一個 */
cluster.on('exit', () => {
setimeout(()=>{
cluster.fork()
}, 5000)
})
} else {
/* 如果是子進程就去加載啟動文件 */
require('./index.js')
process.on('uncaughtException', (err) => {
console.error(err)
/* 進程錯誤上報 */
process.exit(1)
})
}如上我們就可以在異常錯誤的時候重啟線程并異常上報,但是這樣會出現(xiàn)一個問題,那我如果重復(fù)銷毀創(chuàng)建線程可能會進入死循環(huán),我們不確定這個線程的退出是不是可以挽救的情況,所以我們還需要對齊進行完善,首先我們可以在全局監(jiān)控中判斷其內(nèi)存使用的數(shù)量,如果大于我們設(shè)置的限制就讓其退出程序。我們做如下改造防止內(nèi)存泄漏導(dǎo)致的無限重啟:
else {
/* 如果是子進程就去加載啟動文件 */
require('./index.js')
process.on('uncaughtException', (err) => {
console.error(err)
/* 進程錯誤上報 */
/* 如果程序內(nèi)存大于xxxm了讓其退出 */
if(process.memoryUsage().rss > 734003200){
console.log('大于700m了,退出程序吧');
process.exit(1)
}
/* 退出程序 */
process.exit(1)
})
}這樣呢我們就可以對內(nèi)存泄漏問題進行處理了,同時我們還得考慮一種情況,如果子線程假死了怎么辦,僵尸進程如何處理?
心跳檢測,殺掉僵尸進程
實現(xiàn)這個的思路并不負責(zé),和我們?nèi)粘W?strong>ws類似, 主進程發(fā)心跳包,子進程接收并回應(yīng)心跳包,我們分別改造兩個文件,
const cluster = require('cluster')
const os = require('os')
/* 判斷如果是主線程那么就啟動三個子線程 */
if(cluster.isMaster){
/* 多少個cpu啟動多少個子進程 */
for (let i = 0; i < os.cpus().length; i++) {
let timer = null;
/* 記錄每一個woker */
const worker = cluster.fork()
/* 記錄心跳次數(shù) */
let missedPing = 0;
/* 每五秒發(fā)送一個心跳包 并記錄次數(shù)加1 */
timer = setInterval(() => {
missedPing++
worker.send('ping')
/* 如果大于5次都沒有得到響應(yīng)說明可能掛掉了就退出 并清楚定時器 */
if(missedPing > 5 ){
process.kill(worker.process.pid)
worker.send('ping')
clearInterval(timer)
}
}, 5000);
/* 如果接收到心跳響應(yīng)就讓記錄值-1回去 */
worker.on('message', (msg) => {
msg === 'pong' && missedPing--
})
}
/* 如果有線程退出了,我們重啟一個 */
cluster.on('exit', () => {
cluster.fork()
})
} else {
/* 如果是子進程就去加載啟動文件 */
require('./index.js')
/* 心跳回應(yīng) */
process.on('message', (msg) => {
msg === 'ping' && process.send('pong')
})
process.on('uncaughtException', (err) => {
console.error(err)
/* 進程錯誤上報 */
/* 如果程序內(nèi)存大于xxxm了讓其退出 */
if(process.memoryUsage().rss > 734003200){
console.log('大于700m了,退出程序吧');
process.exit(1)
}
/* 退出程序 */
process.exit(1)
})
}介紹一下流程
- 主線程每隔五秒發(fā)送一個心跳包ping,同時記錄上發(fā)送次數(shù)+1,時間根據(jù)自己而定 這里五秒是測試方便
- 子線程接收到了ping信號回復(fù)一個pong
- 主線程接收到了子線程響應(yīng)讓計算數(shù)-1
- 如果大于五次都還沒響應(yīng)可能是假死了,那么退出線程并清空定時器,
至此一個健壯的NodeJs服務(wù)已經(jīng)完成了。
新聞標題:「NodeJs進階」超全面的Node.js性能優(yōu)化相關(guān)知識梳理
標題鏈接:http://www.dlmjj.cn/article/cosiodc.html


咨詢
建站咨詢
