服務(wù)性能測(cè)試
調(diào)試 Node 性能首先得找到性能瓶頸所在,包括兩個(gè)方面:
- top庸论, 測(cè)試 CPU 和內(nèi)存
- iostat, io 設(shè)備的帶寬(硬盤)
Node 性能分析工具
CPU 分析優(yōu)化
工具
profile 探測(cè)器 + ab 壓測(cè)
Node 自帶的 profile
- 啟動(dòng) node 程序,運(yùn)行
node --prof server.js
會(huì)生成 log 文件 - 壓測(cè) 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)志
- 啟動(dòng) node 程序 node --inspect-brk server.js
- 打開 Chrome 的 inspect 切換到 Profiler奥邮,啟動(dòng) CPU 監(jiān)控
- 壓測(cè)
- 暫停万牺、分析結(jié)果
Heavy 模式
Chart 模式
clinic 工具
npm install clinic -g
根據(jù)性能分析報(bào)告優(yōu)化程序
Javascript 代碼性能優(yōu)化
計(jì)算性能優(yōu)化的本質(zhì)
- 減少不必要的計(jì)算
- 空間換取時(shí)間
HTTP 服務(wù)性能優(yōu)化準(zhǔn)則
-
提前計(jì)算(計(jì)算是否可以提前到服務(wù)啟動(dòng)階段
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
- 壓測(cè)
- 壓測(cè)過(guò)程中抓取快照
為了便于直觀的觀察內(nèi)存的占用度液,在請(qǐng)求中創(chuàng)建數(shù)組 生成 長(zhǎng)度為 100w 的數(shù)組
壓測(cè):
ab -c100 -t10 http://127.0.0.1:3000/api/1000000
壓測(cè)(請(qǐng)求)過(guò)程中抓取的快照 內(nèi)存占用(318M)
壓測(cè)(請(qǐng)求)結(jié)束后抓取的快照 內(nèi)存占用(7.7M)
比較兩次快照
制造內(nèi)存泄漏的場(chǎng)景
創(chuàng)建不被釋放的數(shù)組厕宗,每次請(qǐng)求往數(shù)組中添加元素
壓測(cè)前后的內(nèi)存使用情況
都是沒(méi)有釋放的數(shù)組,還指出了變量名堕担,執(zhí)行棧(函數(shù))
優(yōu)化內(nèi)存
Buffer 分配內(nèi)存
自己編寫 c++插件
-
子進(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)的資源
-
Node 子進(jìn)程和線程
- 主線程運(yùn)行 V8 和 js
- 多個(gè)子線程通過(guò)事件循環(huán)被調(diào)度
- 使用子進(jìn)程或線程利用更多的 CPU 資源
-
集群模式
- 集群模式可以監(jiān)聽(tīng)相同的端口
- 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 的實(shí)例,其實(shí)還是發(fā)布訂閱模式 基于 Event 事件實(shí)現(xiàn)的
// 向子進(jìn)程發(fā)送消息
child_process.send('from master!')
// 監(jiān)聽(tīng)子進(jìn)程消息
child_process.on('message', data => {
console.log('from child, port is --- ', data)
})
// 子進(jìn)程退出時(shí)重啟
child_process.on('exit', () => {
console.log('child_process is died --- ' + child_process.pid)
createWorker()
})
// 子進(jìn)程無(wú)法被復(fù)制創(chuàng)建霹购、殺死佑惠、發(fā)送消息時(shí)觸發(fā)
child_process.on('error', () => {
console.log('')
})
}
for (let i = 0; i < cpus; i++) {
createWorker()
}
// 創(chuàng)建子進(jìn)程后主進(jìn)程沒(méi)有退出,如果和子進(jìn)程沒(méi)有事件回調(diào)交互的話需要手動(dòng)退出主進(jìn)程齐疙。所以這就是為什么一般根據(jù)cpu核數(shù)創(chuàng)建子進(jìn)程膜楷,因?yàn)樽舆M(jìn)程開啟后主進(jìn)程退出了。
// 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')
}
})
// 處理沒(méi)有捕獲到的錯(cuò)誤
process.on('uncaughtException', err => {
console.error(err)
process.exit(1)
})
// 監(jiān)聽(tīng)內(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)聽(tīng)子進(jì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īng)子進(jì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)化
動(dòng)靜分離
靜態(tài)內(nèi)容
Q:基本不會(huì)變動(dòng),也不會(huì)因?yàn)檎?qǐng)求參數(shù)的不同而變化的
A: 使用 CDN 分發(fā),HTTP 緩存
動(dòng)態(tài)內(nèi)容
Q:各種因?yàn)檎?qǐng)求參數(shù)不同而變動(dòng)注益,且變動(dòng)的數(shù)量幾乎不可枚舉
A:用大量的源站機(jī)器承載,結(jié)合反響代理進(jìn)行負(fù)載均衡
反向代理和緩存服務(wù)
Nginx 反向代理
redis 緩存