服務(wù)性能測試
調(diào)試 Node 性能首先得找到性能瓶頸所在,包括兩個方面:
- top, 測試 CPU 和內(nèi)存
- iostat, io 設(shè)備的帶寬(硬盤)
Node 性能分析工具
CPU 分析優(yōu)化
工具
profile 探測器 + ab 壓測
Node 自帶的 profile
- 啟動 node 程序,運行
node --prof server.js會生成 log 文件 - 壓測 ab -c100 -t10 http://127.0.0.1:3000/dowload
- 執(zhí)行 log 文件:node --prof-process xxx.log > profile.txt
--inspect-brk 參數(shù)
要想在應(yīng)用代碼的第一行斷開,可以傳入 --inspect-brk 標(biāo)志
- 啟動 node 程序 node --inspect-brk server.js
- 打開 Chrome 的 inspect 切換到 Profiler,啟動 CPU 監(jiān)控
- 壓測
- 暫停、分析結(jié)果
Heavy 模式

image.png
Chart 模式

image.png
clinic 工具
npm install clinic -g
根據(jù)性能分析報告優(yōu)化程序
Javascript 代碼性能優(yōu)化
計算性能優(yōu)化的本質(zhì)
- 減少不必要的計算
- 空間換取時間
HTTP 服務(wù)性能優(yōu)化準(zhǔn)則
-
提前計算(計算是否可以提前到服務(wù)啟動階段
image.png
內(nèi)存分析優(yōu)化
GC
新生代,容量小,垃圾回收快
老生代,容量大,垃圾回收慢
減少內(nèi)存使用,提高服務(wù)性能
不要出現(xiàn)內(nèi)存泄露。
分析內(nèi)存使用情況
--inspect-brk 調(diào)試
- node --inspect-brk-server.js
- 打開 chrome 控制面板,切到 Memory
- 壓測
- 壓測過程中抓取快照
為了便于直觀的觀察內(nèi)存的占用,在請求中創(chuàng)建數(shù)組 生成 長度為 100w 的數(shù)組
壓測:
ab -c100 -t10 http://127.0.0.1:3000/api/1000000
壓測(請求)過程中抓取的快照 內(nèi)存占用(318M)

image.png
壓測(請求)結(jié)束后抓取的快照 內(nèi)存占用(7.7M)

image.png
比較兩次快照

image.png
制造內(nèi)存泄漏的場景
創(chuàng)建不被釋放的數(shù)組,每次請求往數(shù)組中添加元素
壓測前后的內(nèi)存使用情況

image.png
都是沒有釋放的數(shù)組,還指出了變量名,執(zhí)行棧(函數(shù))

image.png
優(yōu)化內(nèi)存
Buffer 分配內(nèi)存
自己編寫 c++插件
-
子進程與線程
- 進程
- 操作系統(tǒng)掛載運行程序的單元
- 擁有獨立的資源,比如內(nèi)存
- 線程
- 進行運算調(diào)度的單元
- 進程內(nèi)的線程共享進程內(nèi)的資源
-
Node 子進程和線程
- 主線程運行 V8 和 js
- 多個子線程通過事件循環(huán)被調(diào)度
- 使用子進程或線程利用更多的 CPU 資源
-
集群模式
- 集群模式可以監(jiān)聽相同的端口
- lib > net.js > listenInCluster 方法
child_process
// worker.js
const http = require('http')
const port = Math.round((Math.random() + 1) * 1000)
http
.createServer((req, res) => {
res.end(port + '')
})
.listen(port, () => {
console.log('runing: ', port)
})
process.on('message', data => {
console.log('child', port, data)
process.send('from child --- ' + port)
})
// master.js
const os = require('os')
const { fork } = require('child_process')
const cpus = os.cpus().length
const createWorker = () => {
const child_process = fork(__dirname + '/worker.js') // ChildProcess 的實例,其實還是發(fā)布訂閱模式 基于 Event 事件實現(xiàn)的
// 向子進程發(fā)送消息
child_process.send('from master!')
// 監(jiān)聽子進程消息
child_process.on('message', data => {
console.log('from child, port is --- ', data)
})
// 子進程退出時重啟
child_process.on('exit', () => {
console.log('child_process is died --- ' + child_process.pid)
createWorker()
})
// 子進程無法被復(fù)制創(chuàng)建、殺死、發(fā)送消息時觸發(fā)
child_process.on('error', () => {
console.log('')
})
}
for (let i = 0; i < cpus; i++) {
createWorker()
}
// 創(chuàng)建子進程后主進程沒有退出,如果和子進程沒有事件回調(diào)交互的話需要手動退出主進程。所以這就是為什么一般根據(jù)cpu核數(shù)創(chuàng)建子進程,因為子進程開啟后主進程退出了。
// setTimeout(() => {
// process.exit(1)
// }, 2000)
cluster
// app.js
const http = require('http')
http
.createServer((req, res) => {
res.writeHead(200)
res.end('hello world')
})
.listen(8000)
process.on('message', msg => {
if (msg === 'ping') {
process.send('pang')
}
})
// 處理沒有捕獲到的錯誤
process.on('uncaughtException', err => {
console.error(err)
process.exit(1)
})
// 監(jiān)聽內(nèi)存泄漏
setInterval(() => {
if (process.memoryUsage.memoryUsage().rss > 700 * 1024 * 1024) {
console.log('memory leak')
process.exit(1)
}
}, 5000)
// index.js
const cluster = require('cluster')
const process = require('process')
const cpus = require('os').cpus().length
const createWorker = () => {
let count = 0
const worker = cluster.fork()
// 監(jiān)聽子進程是否還在工作
setInterval(() => {
worker.send('ping')
count++
if (count > 3) {
worker.exit(1)
}
}, 3000)
worker.on('message', msg => {
if (msg === 'pang') {
count--
}
})
}
if (cluster.isPrimary) {
console.log(`Primary ${process.pid} is running`)
// Fork workers
for (let i = 0; i < cpus / 2; i++) {
createWorker()
}
// 監(jiān)聽子進程狀態(tài)
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`)
setTimeout(() => {
createWorker()
}, 5000)
})
} else {
require('./app')
}
架構(gòu)優(yōu)化
動靜分離
靜態(tài)內(nèi)容
Q:基本不會變動,也不會因為請求參數(shù)的不同而變化的
A: 使用 CDN 分發(fā),HTTP 緩存
動態(tài)內(nèi)容
Q:各種因為請求參數(shù)不同而變動,且變動的數(shù)量幾乎不可枚舉
A:用大量的源站機器承載,結(jié)合反響代理進行負(fù)載均衡
反向代理和緩存服務(wù)
Nginx 反向代理
redis 緩存
