国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

「NodeJs進(jìn)階」超全面的 Node.js 性能優(yōu)化相關(guān)知識梳理

這篇具有很好參考價值的文章主要介紹了「NodeJs進(jìn)階」超全面的 Node.js 性能優(yōu)化相關(guān)知識梳理。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

相信對于前端同學(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ù)吧
「NodeJs進(jìn)階」超全面的 Node.js 性能優(yōu)化相關(guān)知識梳理

上面的東西呢其實(shí)已經(jīng)很直觀了,最開頭的部分就是每秒請求成功了多少個,其次就是請求地址、端口、路徑、大小、這些其實(shí)不是很重要,我們在瀏覽器中自己也可以看到,我們主要需要注意的性能指標(biāo)是下面這些參數(shù):
「NodeJs進(jìn)階」超全面的 Node.js 性能優(yōu)化相關(guān)知識梳理

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

「NodeJs進(jìn)階」超全面的 Node.js 性能優(yōu)化相關(guān)知識梳理

然后我們可以看到other中有一個「Target」,上面輸出了版本,我們只需要點(diǎn)擊最后一行的那個「inspect」就可以進(jìn)入調(diào)試了。進(jìn)入之后我們發(fā)現(xiàn),上面就可以完完整整看到我們寫的源代碼了。

「NodeJs進(jìn)階」超全面的 Node.js 性能優(yōu)化相關(guā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é)果如下:

「NodeJs進(jìn)階」超全面的 Node.js 性能優(yōu)化相關(guān)知識梳理

我們也可點(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é)束再截圖一份,也可以在壓測過程中多次截圖,得到如下:

「NodeJs進(jìn)階」超全面的 Node.js 性能優(yōu)化相關(guān)知識梳理

我們在壓測過程中不斷截取快照發(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)完成了。

如果對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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包