[譯]保持Node.js的速度-建立高性能Node.js Servers的工具、技術和提示

pre-tips

本文翻譯自: Keeping Node.js Fast: Tools, Techniques, And Tips For Making High-Performance Node.js Servers html

原文地址:https://www.smashingmagazine....前端

中文標題:保持Node.js的速度-建立高性能Node.js Servers的工具、技術和提示java

快速摘要

Node 是一個很是多彩的平臺,而建立network服務就是其很是重要的能力之一。在本文咱們將關注最主流的: HTTP Web servers.node

引子

若是你已經使用Node.js足夠長的時間,那麼毫無疑問你會碰到比較痛苦的速度問題。JavaScript是一種事件驅動的、異步的語言。這很明顯使得對性能的推理變得棘手。Node.js的迅速普及使得咱們必須尋找適合這種server-side javacscript的工具、技術。c++

當咱們碰到性能問題,在瀏覽器端的經驗將沒法適用於服務器端。因此咱們如何確保一個Node.js代碼是快速的且能達到咱們的要求呢?讓咱們來動手看一些實例git

工具

咱們須要一個工具來壓測咱們的server從而測量性能。好比,咱們使用 autocannongithub

npm install -g autocannon // 或使用淘寶源cnpm, 騰訊源tnpm

其餘的Http benchmarking tools 包括 Apache Bench(ab)wrk2, 但AutoCannon是用Node寫的,對前端來講會更加方便並易於安裝,它能夠很是方便的安裝在 Windows、Linux 和Mac OS X.apache

當咱們安裝了基準性能測試工具,咱們須要用一些方法去診斷咱們的程序。一個很不錯的診斷性能問題的工具即是 Node Clinic 。它也能夠用npm安裝:npm

npm install -g clinic

這實際上會安裝一系列套件,咱們將使用 Clinic Doctor
和 Clinic Flame (一個 ox 的封裝)json

譯者注: ox是一個自動剖析cpu並生成node進程火焰圖的工具; 而clinic Flame就是基於ox的封裝。
另外一方面, clinic工具自己實際上是一系列套件的組合,它不一樣的子命令分別會調用到不一樣的子模塊,例如:
  • 醫生診斷功能。The doctor functionality is provided by Clinic.js Doctor.
  • 氣泡診斷功能。The bubbleprof functionality is provided by Clinic.js Bubbleprof.
  • 火焰圖功能。 The flame functionality is provided by Clinic.js Flame.)

tips: 對於本文實例,須要 Node 8.11.2 或更高版本

代碼示例

咱們的例子是一個只有一個資源的簡單的 REST server:暴露一個 GET 訪問的路由 /seed/v1 ,返回一個大 JSON 載荷。 server端的代碼就是一個app目錄,裏面包括一個 packkage.json (依賴 restify 7.1.0)、一個 index.js 和 一個 util.js (譯者注: 放一些工具函數)

// index.js
const restify = require('restify')
const server = restify.createServer()
const { etagger, timestamp, fetchContent } from './util'

server.use(etagger.bind(server)) // 綁定etagger中間件,能夠給資源請求加上etag響應頭

server.get('/seed/v1', function () {
  fetchContent(req.url, (err, content) => {
    if (err) {
      return next(err)
    }
    res.send({data: content, ts: timestamp(), url: req.url})
    next()
  })
})

server.listen(8080, function () {
  cosnole.log(' %s listening at %s',  server.name, server.url)
})
// util.js
const restify = require('restify')
const crypto = require('crypto')

module.exports = function () {
    const content = crypto.rng('5000').toString('hex') // 普通有規則的隨機

    const fetchContent = function (url, cb) {
        setImmediate(function () {
        if (url !== '/seed/v1') return restify.errors.NotFoundError('no api!')
            cb(content)
        })
    }
    
    let last = Date.now()
    const TIME_ONE_MINUTE = 60000
    const timestamp = function () {
      const now = Date.now()
      if (now - last >= TIME_ONE_MINITE) {
          last = now
      }
      return last
    }
    
    const etagger = function () {
        const cache = {}
        let afterEventAttached  = false
        function attachAfterEvent(server) {
            if (attachAfterEvent ) return
            afterEventAttached  = true
            server.on('after', function (req, res) {
                if (res.statusCode == 200 && res._body != null) {
                    const urlKey = crpto.createHash('sha512')
                        .update(req.url)
                        .digets()
                        .toString('hex')
                    const contentHash = crypto.createHash('sha512')
                    .update(JSON.stringify(res._body))
                    .digest()
                    .toString('hex')
                    if (cache[urlKey] != contentHash) cache[urlKey] = contentHash
                }
            })
        }
         return function(req, res, next) {
                // 譯者注: 這裏attachEvent的位置好像不太優雅,我換另外一種方式改了下這裏。能夠參考: https://github.com/cuiyongjian/study-restify/tree/master/app
                attachAfterEvent(this) // 給server註冊一個after鉤子,每次即將響應數據時去計算body的etag值
            const urlKey = crypto.createHash('sha512')
            .update(req.url)
            .digest()
            .toString('hex')
            // 譯者注: 這裏etag的返回邏輯應該有點小問題,每次請求都是返回的上次寫入cache的etag
            if (urlKey in cache) res.set('Etag', cache[urlKey])
            res.set('Cache-Control', 'public; max-age=120')
        }
    }
    
    return { fetchContent, timestamp, etagger }
}

務必不要用這段代碼做爲最佳實踐,由於這裏面有不少代碼的壞味道,可是咱們接下來將測量並找出這些問題。

要得到這個例子的源碼能夠去這裏

Profiling 剖析

爲了剖析咱們的代碼,咱們須要兩個終端窗口。一個用來啓動app,另一個用來壓測他。

第一個terminal,咱們執行:

node ./index.js

另一個terminal,咱們這樣剖析他(譯者注: 實際是在壓測):

autocannon -c100 localhost:3000/seed/v1

這將打開100個併發請求轟炸服務,持續10秒。

結果大概是下面這個樣子:

stat avg stdev Max
耗時(毫秒) 3086.81 1725.2 5554
吞吐量(請求/秒) 23.1 19.18 65
每秒傳輸量(字節/秒) 237.98 kB 197.7 kB 688.13 kB
231 requests in 10s, 2.4 MB read

結果會根據你機器狀況變化。然而咱們知道: 通常的「Hello World」Node.js服務器很容易在一樣的機器上每秒完成三萬個請求,如今這段代碼只能承受每秒23個請求且平均延遲超過3秒,這是使人沮喪的。

譯者注: 我用公司macpro18款 15寸 16G 256G,測試結果以下:

圖片描述

診斷

定位問題

咱們能夠經過一句命令來診斷應用,感謝 clinic doctor 的 -on-port 命令。在app目錄下,咱們執行:

clinic doctor --on-port='autocannon -c100 localhost:3000/seed/v1' -- node index.js
譯者注:
如今autocannon的話可使用新的subarg形式的命令語法:
clinic doctor --autocannon [ /seed/v1 -c 100 ] -- node index.js

clinic doctor會在剖析完畢後,建立html文件並自動打開瀏覽器。

結果長這個樣子:

圖片描述

譯者的測試長這樣子:
--on-port語法

--autocannon語法

譯者注:橫座標實際上是你係統時間,冒號後面的表示當前的系統時間的 - 秒數。
備註:接下來的文章內容分析,咱們仍是以原文的統計結果圖片爲依據。

跟隨UI頂部的消息,咱們看到 EventLoop 圖表,它的確是紅色的,而且這個EventLoop延遲在持續增加。在咱們深刻研究他意味着什麼以前,咱們先了解下其餘指標下的診斷。

咱們能夠看到CPU一直在100%或超過100%這裏徘徊,由於進程正在努力處理排隊的請求。Node的 JavaScript 引擎(也就是V8) 着這裏實際上用 2 個 CPU核心在工做,由於機器是多核的 而V8會用2個線程。 一個線程用來執行 EventLoop,另一個線程用來垃圾收集。 當CPU高達120%的時候就是進程在回收處理完的請求的遺留對象了(譯者注: 操做系統的進程CPU使用率的確常常會超過100%,這是由於進程內用了多線程,OS把工做分配到了多個核心,所以統計cpu佔用時間時會超過100%)

咱們看與之相關的內存圖表。實線表示內存的堆內存佔用(譯者注:RSS表示node進程實際佔用的內存,heapUsage堆內存佔用就是指的堆區域佔用了多少,THA就表示總共申請到了多少堆內存。通常看heapUsage就好,由於他表示了node代碼中大多數JavaScript對象所佔用的內存)。咱們看到,只要CPU圖表上升一下則堆內存佔用就降低一些,這表示內存正在被回收。

activeHandler跟EventLoop的延遲沒有什麼相關性。一個active hanlder 就是一個表達 I/O的對象(好比socket或文件句柄) 或者一個timer (好比setInterval)。咱們用autocannon建立了100鏈接的請求(-c100), activehandlers 保持在103. 額外的3個handler實際上是 STDOUT,STDERROR 以及 server 對象自身(譯者: server自身也是個socket監聽句柄)。

若是咱們點擊一下UI界面上底部的建議pannel面板,咱們會看到:
圖片描述

短時間緩解

深刻分析性能問題須要花費大量的時間。在一個現網項目中,能夠給服務器或服務添加過載保護。過載保護的思路就是檢測 EventLoop 延遲(以及其餘指標),而後在超過閾值時響應一個 "503 Service Unavailable"。這就可讓 負載均衡器轉向其餘server實例,或者實在不行就讓用戶過一會重試。overload-protection-module 這個過載保護模塊能直接低成本地接入到 Express、Koa 和 Restify使用。Hapi 框架也有一個配置項提供一樣的過載保護。(譯者注:實際上看overload-protection模塊的底層就是經過loopbench 實現的EventLoop延遲採樣,而loopbench就是從Hapi框架裏抽離出來的一個模塊;至於內存佔用,則是overload-protection內部本身實現的採樣,畢竟直接用memoryUsage的api就行了)

理解問題所在

就像 Clinic Doctor 說的,若是 EventLoop 延遲到咱們觀察的這個樣子,極可能有一個或多個函數阻塞了事件循環。認識到Node.js的這個主要特性很是重要:在當前的同步代碼執行完成以前,異步事件是沒法被執行的。這就是爲何下面 setTimeout 不能按照預料的時間觸發的緣由。

舉例,在瀏覽器開發者工具或Node.js的REPL裏面執行:

console.time('timeout')
setTimeout(console.timeEnd, 100, 'timeout')
let n = 1e7
while (n--) Math.random()

這個打印出的時間永遠不會是100ms。它將是150ms到250ms之間的一個數字。setTimeoiut 調度了一個異步操做(console.timeEnd),可是當前執行的代碼沒有完成;下面有額外兩行代碼來作了一個循環。當前所執行的代碼一般被叫作「Tick」。要完成這個 Tick,Math.random 須要被調用 1000 萬次。若是這會花銷 100ms,那麼timeout觸發時的總時間就是 200ms (再加上setTimeout函數實際推入隊列時的延時,約幾毫秒)

譯者注: 實際上這裏做者的解釋有點小問題。首先這個例子假如按他所說循環會耗費100毫秒,那麼setTimeout觸發時也是100ms而已,不會是兩個時間相加。由於100毫秒的循環結束,setTimeout也要被觸發了。
另外:你實際電腦測試時,極可能像我同樣獲得的結果是 100ms多一點,而不是做者的150-250之間。做者之因此獲得 150ms,是由於它使用的電腦性能緣由使得 while(n--) 這個循環所花費的時間是 150ms到250ms。而一旦性能好一點的電腦計算1e7次循環只需幾十毫秒,徹底不會阻塞100毫秒以後的setTimeout,這時獲得的結果每每是103ms左右,其中的3ms是底層函數入隊和調用花掉的時間(跟這裏所說的問題無關)。所以,你本身在測試時能夠把1e7改爲1e8試試。總之讓他的執行時間超過100毫秒。

在服務器端上下文若是一個操做在當前 Tick 中執行時間很長,那麼就會致使請求沒法被處理,而且數據也沒法獲取(譯者注:好比處理新的網絡請求或處理讀取文件的IO事件),由於異步代碼在當前 Tick 完成以前沒法執行。這意味着計算昂貴的代碼將會讓server全部交互都變得緩慢。因此建議你拆分資源敏感的任務到單獨的進程裏去,而後從main主server中去調用它,這能避免那些不多使用但資源敏感(譯者注: 這裏特指CPU敏感)的路由拖慢了那些常常訪問但資源不敏感的路由的性能(譯者注:就是不要讓某個cpu密集的路徑拖慢整個node應用)。

本文的例子server中有不少代碼阻塞了事件循環,因此下一步咱們來定位這個代碼的具體位置所在。

分析

定位性能問題的代碼的一個方法就是建立和分析「火焰圖」。一個火焰圖將函數表達爲彼此疊加的塊---不是隨着時間的推移而是聚合。之因此叫火焰圖是由於它用橘黃到紅色的色階來表示,越紅的塊則表示是個「熱點」函數,意味着極可能會阻塞事件循環。獲取火焰圖的數據須要經過對CPU進行採樣---即node中當前執行的函數及其堆棧的快照。而熱量(heat)是由一個函數在分析期間處於棧頂執行所佔用的時間百分比決定的。若是它不是當前棧中最後被調用的那個函數,那麼他就極可能會阻塞事件循環。

讓咱們用 clinic flame 來生成示例代碼的火焰圖:

clinic flame --on-port=’autocannon -c100 localhost:$PORT/seed/v1’ -- node index.js
譯者注: 也可使用新版命令風格:
clinic flame --autocannon [ /seed/v1 -c200 -d 10 ] -- node index.js

結果會自動展現在你的瀏覽器中:

Clinic可視化火焰圖

譯者注: 新版變成下面這副樣子了,功能更強大,但可能得學習下怎麼看。。

圖片描述

(譯者注:下面分析時仍是看原文的圖)
塊的寬度表示它花費了多少CPU時間。能夠看到3個主要堆棧花費了大部分的時間,而其中 server.on 這個是最紅的。 實際上,這3個堆棧是相同的。他們之因此分開是由於在分析期間優化過的和未優化的函數會被視爲不一樣的調用幀。帶有 * 前綴的是被JavaScript引擎優化過的函數,而帶有 ~ 前綴的是未優化的。若是是否優化對咱們的分析不重要,咱們能夠點擊 Merge 按鈕把它們合併。這時圖像會變成這樣:

圖片描述

從開始看,咱們能夠發現出問題的代碼在 util.js 裏。這個過慢的函數也是一個 event handler:觸發這個函數的來源是Node核內心的 events 模塊,而 server.on 是event handler匿名函數的一個後備名稱。咱們能夠看到這個代碼跟實際處理本次request請求的代碼並不在同一個 Tick 當中(譯者注: 若是在同一個Tick就會用一個堆棧圖豎向堆疊起來)。若是跟request處理在同一個 Tick中,那堆棧中應該是Node的 http 模塊、net和stream模塊

若是你展開其餘的更小的塊你會看到這些Http的Node核心函數。好比嘗試下右上角的search,搜索關鍵詞 send(restify和http內部方法都有send方法)。而後你能夠發現他們在火焰圖的右邊(函數按字母排序)(譯者注:右側藍色高亮的區域)

搜索http處理函數

能夠看到實際的 HTTP 處理塊佔用時間相對較少。

咱們能夠點擊一個高亮的青色塊來展開,看到裏面 http_outgoing.js 文件的 writeHead、write函數(Node核心http庫中的一部分)

圖片描述

咱們能夠點擊 all stack 返回到主要視圖。

這裏的關鍵點是,儘管 server.on 函數跟實際 request處理代碼不在一個 Tick中,它依然能經過延遲其餘正在執行的代碼來影響了server的性能。

Debuging 調試

咱們如今從火焰圖知道了問題函數在 util.js 的 server.on 這個eventHandler裏。咱們來瞅一眼:

server.on('after', (req, res) => {
  if (res.statusCode !== 200) return
  if (!res._body) return
  const key = crypto.createHash('sha512')
    .update(req.url)
    .digest()
    .toString('hex')
  const etag = crypto.createHash('sha512')
    .update(JSON.stringify(res._body))
    .digest()
    .toString('hex')
  if (cache[key] !== etag) cache[key] = etag
})

衆所周知,加密過程都是很昂貴的cpu密集任務,還有序列化(JSON.stringify),可是爲何火焰圖中看不到呢?實際上在採樣過程當中都已經被記錄了,只是他們隱藏在 cpp過濾器 內 (譯者注:cpp就是c++類型的代碼)。咱們點擊 cpp 按鈕就能看到以下的樣子:

解開序列化和加密的圖

與序列化和加密相關的內部V8指令被展現爲最熱的區域堆棧,而且花費了最多的時間。 JSON.stringify 方法直接調用了 C++代碼,這就是爲何咱們看不到JavaScript 函數。在加密這裏, createHashupdate 這樣的函數都在數據中,而他們要麼內聯(合併並消失在merge視圖)要麼佔用時間過小沒法展現。

一旦咱們開始推理etagger函數中的代碼,很快就會發現它的設計很糟糕。爲何咱們要從函數上下文中獲取服務器實例?全部這些hash計算都是必要的嗎?在實際場景中也沒有If-None-Match頭支持,若是用if-none-match這將減輕某些真實場景中的一些負載,由於客戶端會發出頭請求來肯定資源的新鮮度。

讓咱們先忽略全部這些問題,先驗證一下 server.on 中的代碼是不是致使問題的緣由。咱們能夠把 server.on 裏面的代碼作成空函數而後生成一個新的火焰圖。

如今 etagger 函數變成這樣:

function etagger () {
  var cache = {}
  var afterEventAttached = false
  function attachAfterEvent (server) {
    if (attachAfterEvent === true) return
    afterEventAttached = true
    server.on('after', (req, res) => {})
  }
  return function (req, res, next) {
    attachAfterEvent(this)
    const key = crypto.createHash('sha512')
      .update(req.url)
      .digest()
      .toString('hex')
    if (key in cache) res.set('Etag', cache[key])
    res.set('Cache-Control', 'public, max-age=120')
    next()
  }
}

如今 server.on 的事件監聽函數是個以空函數 no-op. 讓咱們再次執行 clinic flame:

clinic flame --on-port='autocannon -c100 localhost:$PORT/seed/v1' -- node index.js
Copy

會生成以下的火焰圖:
server.on爲空函數後的火焰圖

這看起來好一些,咱們會看到每秒吞吐量有所增加。可是爲何 event emit 的代碼這麼紅? 咱們指望的是此時 HTTP 處理要佔用最多的CPU時間,畢竟 server.on 裏面已經什麼都沒作了。

這種類型的瓶頸一般由於一個函數調用超出了必定指望的程度。

util.js 頂部的這一句可疑的代碼多是一個線索:

require('events').defaultMaxListeners = Infinity

讓咱們移除掉這句代碼,而後啓動咱們的應用,帶上 --trace-warnings flag標記。

node --trace-warnings index.js

若是咱們在下一個teminal中執行壓測:

autocannon -c100 localhost:3000/seed/v1

會看到咱們的進程輸出一些:

(node:96371) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 after listeners added. Use emitter.setMaxListeners() to increase limit
  at _addListener (events.js:280:19)
  at Server.addListener (events.js:297:10)
  at attachAfterEvent 
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/util.js:22:14)
  at Server.
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/util.js:25:7)
  at call
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/chain.js:164:9)
  at next
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/chain.js:120:9)
  at Chain.run
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/chain.js:123:5)
  at Server._runUse
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/server.js:976:19)
  at Server._runRoute
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/server.js:918:10)
  at Server._afterPre
    (/Users/davidclements/z/nearForm/keeping-node-fast/slow/node_modules/restify/lib/server.js:888:10)

Node 告訴咱們有太多的事件添加到了 server 對象上。這很奇怪,由於咱們有一句判斷,若是 after 事件已經綁定到了 server,則直接return。因此首次綁定以後,只有一個 no-op 函數綁到了 server上。

讓咱們看下 attachAfterEvent 函數:

var afterEventAttached = false
function attachAfterEvent (server) {
  if (attachAfterEvent === true) return
  afterEventAttached = true
  server.on('after', (req, res) => {})
}

咱們發現條件檢查語句寫錯了! 不該該是 attachAfterEvent ,而是 afterEventAttached. 這意味着每一個請求都會往 server 對象上添加一個事件監聽,而後每一個請求的最後全部的以前綁定上的事件都要觸發。唉呀媽呀!

優化

既然知道了問題所在,讓咱們看看如何讓咱們的server更快

低端優化 (容易摘到的果子)

讓咱們還原 server.on 的代碼(不讓他是空函數了)而後條件語句中改爲正確的 boolean 判斷。如今咱們的 etagger 函數這樣:

function etagger () {
  var cache = {}
  var afterEventAttached = false
  function attachAfterEvent (server) {
    if (afterEventAttached === true) return
    afterEventAttached = true
    server.on('after', (req, res) => {
      if (res.statusCode !== 200) return
      if (!res._body) return
      const key = crypto.createHash('sha512')
        .update(req.url)
        .digest()
        .toString('hex')
      const etag = crypto.createHash('sha512')
        .update(JSON.stringify(res._body))
        .digest()
        .toString('hex')
      if (cache[key] !== etag) cache[key] = etag
    })
  }
  return function (req, res, next) {
    attachAfterEvent(this)
    const key = crypto.createHash('sha512')
      .update(req.url)
      .digest()
      .toString('hex')
    if (key in cache) res.set('Etag', cache[key])
    res.set('Cache-Control', 'public, max-age=120')
    next()
  }
}

如今,咱們再來執行一次 Profile(進程剖析,進程描述)。

node index.js

而後用 autocanno 來profile 它:

autocannon -c100 localhost:3000/seed/v1

咱們看到結果顯示有200倍的提高(持續10秒 100個併發)

圖片描述

平衡開發成本和潛在的服務器成本也很是重要。咱們須要定義咱們在優化時要走多遠。不然咱們很容易將80%的時間投入到20%的性能提升上。項目是否能承受?

在一些場景下,用 低端優化 來花費一天提升200倍速度才被認爲是合理的。而在某些狀況下,咱們可能但願不惜一切讓咱們的項目盡最大最大最大可能的快。這種抉擇要取決於項目優先級。

控制資源支出的一種方法是設定目標。例如,提升10倍,或達到每秒4000次請求。基於業務需求的這一種方式最有意義。例如,若是服務器成本超出預算100%,咱們能夠設定2倍改進的目標

更進一步

若是咱們再作一張火焰圖,咱們會看到:

圖片描述

事件監聽器依然是一個瓶頸,它依然佔用了 1/3 的CPU時間 (它的寬度大約是整行的三分之一)

(譯者注: 在作優化以前可能每次都要作這樣的思考:) 經過優化咱們能得到哪些額外收益,以及這些改變(包括相關聯的代碼重構)是否值得?

==============

咱們看最終終極優化(譯者注:終極優化指的是做者在後文提到的另一些方法)後能達到的性能特徵(持續執行十秒 http://localhost:3000/seed/v1 --- 100個併發鏈接)

92k requests in 11s, 937.22 MB read[15]

儘管終極優化後 1.6倍 的性能提升已經很顯著了,但與之付出的努力、改變、代碼重構 是否有必要也是值得商榷的。尤爲是與以前簡單修復一個bug就能提高200倍的性能相比。

爲了實現深度改進,須要使用一樣的技術如:profile分析、生成火焰圖、分析、debug、優化。最後完成優化後的服務器代碼,能夠在這裏查看。

最後提升到 800/s 的吞吐量,使用了以下方法:

這些更改稍微複雜一些,對代碼庫的破壞性稍大一些,並使etagger中間件的靈活性稍微下降,由於它會給路由帶來負擔以提供Etag值。但它在執行Profile的機器上每秒可多增長3000個請求。

讓咱們看看最終優化後的火焰圖:

全部優化以後的健康火焰圖

圖中最熱點的地方是 Node core(node核心)的 net 模塊。這是最指望的狀況。

防止性能問題

完美一點,這裏提供一些在部署以前防止性能問題的建議。

在開發期間使用性能工具做爲非正式檢查點能夠避免把性能問題帶入生產環境。建議將AutoCannon和Clinic(或其餘相似的工具)做爲平常開發工具的一部分。

購買或使用一個框架時,看看他的性能政策是什麼(譯者注:對開源框架就看看benchmark和文檔中的性能建議)。若是框架沒有指出性能相關的,那麼就看看他是否與你的基礎架構和業務目標一致。例如,Restify已明確(自版本7發佈以來)將致力於提高性。可是,若是低成本和高速度是你絕對優先考慮的問題,請考慮使用Fastify,Restify貢獻者測得的速度提升17%。

在選擇一些普遍流行的類庫時要多加留意---尤爲是留意日誌。 在開發者修復issue的時候,他們可能會在代碼中添加一些日誌輸出來幫助他們在將來debug問題。若是她用了一個性能差勁的 logger 組件,這可能會像 溫水煮青蛙 同樣隨着時間的推移扼殺性能。pino 日誌組件是一個 Node.js 中能夠用的速度最快的JSON換行日誌組件。

最後,始終記住Event Loop是一個共享資源。 Node.js服務器的性能會受到最熱路徑中最慢的那個邏輯的約束。

相關文章
相關標籤/搜索