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