Node.js 性能調(diào)優(yōu)

服務(wù)性能測試

調(diào)試 Node 性能首先得找到性能瓶頸所在,包括兩個方面:

  1. top, 測試 CPU 和內(nèi)存
  2. iostat, io 設(shè)備的帶寬(硬盤)

Node 性能分析工具

CPU 分析優(yōu)化

工具

profile 探測器 + ab 壓測

Node 自帶的 profile

文檔

  1. 啟動 node 程序,運行 node --prof server.js 會生成 log 文件
  2. 壓測 ab -c100 -t10 http://127.0.0.1:3000/dowload
  3. 執(zhí)行 log 文件:node --prof-process xxx.log > profile.txt

--inspect-brk 參數(shù)

要想在應(yīng)用代碼的第一行斷開,可以傳入 --inspect-brk 標(biāo)志

  1. 啟動 node 程序 node --inspect-brk server.js
  2. 打開 Chrome 的 inspect 切換到 Profiler,啟動 CPU 監(jiān)控
  3. 壓測
  4. 暫停、分析結(jié)果
Heavy 模式
image.png
Chart 模式
image.png

clinic 工具

npm install clinic -g

根據(jù)性能分析報告優(yōu)化程序

Javascript 代碼性能優(yōu)化

計算性能優(yōu)化的本質(zhì)

  1. 減少不必要的計算
  2. 空間換取時間

HTTP 服務(wù)性能優(yōu)化準(zhǔn)則

  1. 提前計算(計算是否可以提前到服務(wù)啟動階段


    image.png

內(nèi)存分析優(yōu)化

GC
新生代,容量小,垃圾回收快
老生代,容量大,垃圾回收慢
減少內(nèi)存使用,提高服務(wù)性能

不要出現(xiàn)內(nèi)存泄露。

分析內(nèi)存使用情況

--inspect-brk 調(diào)試

  1. node --inspect-brk-server.js
  2. 打開 chrome 控制面板,切到 Memory
  3. 壓測
  4. 壓測過程中抓取快照

為了便于直觀的觀察內(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)存

  1. Buffer 分配內(nèi)存

  2. 自己編寫 c++插件

  3. 子進程與線程

    • 進程
    • 操作系統(tǒng)掛載運行程序的單元
    • 擁有獨立的資源,比如內(nèi)存
    • 線程
    • 進行運算調(diào)度的單元
    • 進程內(nèi)的線程共享進程內(nèi)的資源
  4. Node 子進程和線程

    • 主線程運行 V8 和 js
    • 多個子線程通過事件循環(huán)被調(diào)度
    • 使用子進程或線程利用更多的 CPU 資源
  5. 集群模式

    • 集群模式可以監(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 緩存

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容