成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

[譯]保持Node.js的速度-創(chuàng)建高性能Node.js Servers的工具、技術和提示

Lavender / 2136人閱讀

摘要:本文翻譯自原文地址中文標題保持的速度創(chuàng)建高性能的工具技術和提示快速摘要是一個非常多彩的平臺,而創(chuàng)建服務就是其非常重要的能力之一。在目錄下,我們執(zhí)行譯者注現(xiàn)在的話可以使用新的形式的命令語法會在剖析完畢后,創(chuàng)建文件并自動打開瀏覽器。

pre-tips

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

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

中文標題:保持Node.js的速度-創(chuàng)建高性能Node.js Servers的工具、技術和提示

快速摘要

Node 是一個非常多彩的平臺,而創(chuàng)建network服務就是其非常重要的能力之一。在本文我們將關注最主流的: HTTP Web servers.

引子

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

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

工具

我們需要一個工具來壓測我們的server從而測量性能。比如,我們使用 autocannon

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

其他的Http benchmarking tools 包括 Apache Bench(ab) 和 wrk2, 但AutoCannon是用Node寫的,對前端來說會更加方便并易于安裝,它可以非常方便的安裝在 Windows、Linux 和Mac OS X.

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

npm install -g clinic

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

譯者注: ox是一個自動剖析cpu并生成node進程火焰圖的工具; 而clinic Flame就是基于ox的封裝。
另一方面, clinic工具本身其實是一系列套件的組合,它不同的子命令分別會調用到不同的子模塊,例如:

醫(yī)生診斷功能。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 (譯者注: 放一些工具函數(shù))

// 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") // 普通有規(guī)則的隨機

    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的位置好像不太優(yōu)雅,我換另一種方式改了下這里。可以參考: https://github.com/cuiyongjian/study-restify/tree/master/app
                attachAfterEvent(this) // 給server注冊一個after鉤子,每次即將響應數(shù)據(jù)時去計算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,我們執(zhí)行:

node ./index.js

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

autocannon -c100 localhost:3000/seed/v1

這將打開100個并發(fā)請求轟炸服務,持續(xù)10秒。

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

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

結果會根據(jù)你機器情況變化。然而我們知道: 一般的“Hello World”Node.js服務器很容易在同樣的機器上每秒完成三萬個請求,現(xiàn)在這段代碼只能承受每秒23個請求且平均延遲超過3秒,這是令人沮喪的。

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

診斷 定位問題

我們可以通過一句命令來診斷應用,感謝 clinic doctor 的 -on-port 命令。在app目錄下,我們執(zhí)行:

clinic doctor --on-port="autocannon -c100 localhost:3000/seed/v1" -- node index.js
譯者注:
現(xiàn)在autocannon的話可以使用新的subarg形式的命令語法:
clinic doctor --autocannon [ /seed/v1 -c 100 ] -- node index.js

clinic doctor會在剖析完畢后,創(chuàng)建html文件并自動打開瀏覽器。

結果長這個樣子:

譯者的測試長這樣子:

譯者注:橫坐標其實是你系統(tǒng)時間,冒號后面的表示當前的系統(tǒng)時間的 - 秒數(shù)。
備注:接下來的文章內容分析,我們還是以原文的統(tǒng)計結果圖片為依據(jù)。

跟隨UI頂部的消息,我們看到 EventLoop 圖表,它的確是紅色的,并且這個EventLoop延遲在持續(xù)增長。在我們深入研究他意味著什么之前,我們先了解下其他指標下的診斷。

我們可以看到CPU一直在100%或超過100%這里徘徊,因為進程正在努力處理排隊的請求。Node的 JavaScript 引擎(也就是V8) 著這里實際上用 2 個 CPU核心在工作,因為機器是多核的 而V8會用2個線程。 一個線程用來執(zhí)行 EventLoop,另外一個線程用來垃圾收集。 當CPU高達120%的時候就是進程在回收處理完的請求的遺留對象了(譯者注: 操作系統(tǒng)的進程CPU使用率的確經常會超過100%,這是因為進程內用了多線程,OS把工作分配到了多個核心,因此統(tǒng)計cpu占用時間時會超過100%)

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

activeHandler跟EventLoop的延遲沒有什么相關性。一個active hanlder 就是一個表達 I/O的對象(比如socket或文件句柄) 或者一個timer (比如setInterval)。我們用autocannon創(chuàng)建了100連接的請求(-c100), activehandlers 保持在103. 額外的3個handler其實是 STDOUT,STDERROR 以及 server 對象自身(譯者: server自身也是個socket監(jiān)聽句柄)。

如果我們點擊一下UI界面上底部的建議pannel面板,我們會看到:

短期緩解

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

理解問題所在

就像 Clinic Doctor 說的,如果 EventLoop 延遲到我們觀察的這個樣子,很可能有一個或多個函數(shù)阻塞了事件循環(huán)。認識到Node.js的這個主要特性非常重要:在當前的同步代碼執(zhí)行完成之前,異步事件是無法被執(zhí)行的。這就是為什么下面 setTimeout 不能按照預料的時間觸發(fā)的原因。

舉例,在瀏覽器開發(fā)者工具或Node.js的REPL里面執(zhí)行:

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

這個打印出的時間永遠不會是100ms。它將是150ms到250ms之間的一個數(shù)字。setTimeoiut 調度了一個異步操作(console.timeEnd),但是當前執(zhí)行的代碼沒有完成;下面有額外兩行代碼來做了一個循環(huán)。當前所執(zhí)行的代碼通常被叫做“Tick”。要完成這個 Tick,Math.random 需要被調用 1000 萬次。如果這會花銷 100ms,那么timeout觸發(fā)時的總時間就是 200ms (再加上setTimeout函數(shù)實際推入隊列時的延時,約幾毫秒)

譯者注: 實際上這里作者的解釋有點小問題。首先這個例子假如按他所說循環(huán)會耗費100毫秒,那么setTimeout觸發(fā)時也是100ms而已,不會是兩個時間相加。因為100毫秒的循環(huán)結束,setTimeout也要被觸發(fā)了。
另外:你實際電腦測試時,很可能像我一樣得到的結果是 100ms多一點,而不是作者的150-250之間。作者之所以得到 150ms,是因為它使用的電腦性能原因使得 while(n--) 這個循環(huán)所花費的時間是 150ms到250ms。而一旦性能好一點的電腦計算1e7次循環(huán)只需幾十毫秒,完全不會阻塞100毫秒之后的setTimeout,這時得到的結果往往是103ms左右,其中的3ms是底層函數(shù)入隊和調用花掉的時間(跟這里所說的問題無關)。因此,你自己在測試時可以把1e7改成1e8試試。總之讓他的執(zhí)行時間超過100毫秒。

在服務器端上下文如果一個操作在當前 Tick 中執(zhí)行時間很長,那么就會導致請求無法被處理,并且數(shù)據(jù)也無法獲取(譯者注:比如處理新的網(wǎng)絡請求或處理讀取文件的IO事件),因為異步代碼在當前 Tick 完成之前無法執(zhí)行。這意味著計算昂貴的代碼將會讓server所有交互都變得緩慢。所以建議你拆分資源敏感的任務到多帶帶的進程里去,然后從main主server中去調用它,這能避免那些很少使用但資源敏感(譯者注: 這里特指CPU敏感)的路由拖慢了那些經常訪問但資源不敏感的路由的性能(譯者注:就是不要讓某個cpu密集的路徑拖慢整個node應用)。

本文的例子server中有很多代碼阻塞了事件循環(huán),所以下一步我們來定位這個代碼的具體位置所在。

分析

定位性能問題的代碼的一個方法就是創(chuàng)建和分析“火焰圖”。一個火焰圖將函數(shù)表達為彼此疊加的塊---不是隨著時間的推移而是聚合。之所以叫火焰圖是因為它用橘黃到紅色的色階來表示,越紅的塊則表示是個“熱點”函數(shù),意味著很可能會阻塞事件循環(huán)。獲取火焰圖的數(shù)據(jù)需要通過對CPU進行采樣---即node中當前執(zhí)行的函數(shù)及其堆棧的快照。而熱量(heat)是由一個函數(shù)在分析期間處于棧頂執(zhí)行所占用的時間百分比決定的。如果它不是當前棧中最后被調用的那個函數(shù),那么他就很可能會阻塞事件循環(huán)。

讓我們用 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

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

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

(譯者注:下面分析時還是看原文的圖)
塊的寬度表示它花費了多少CPU時間??梢钥吹?個主要堆棧花費了大部分的時間,而其中 server.on 這個是最紅的。 實際上,這3個堆棧是相同的。他們之所以分開是因為在分析期間優(yōu)化過的和未優(yōu)化的函數(shù)會被視為不同的調用幀。帶有 * 前綴的是被JavaScript引擎優(yōu)化過的函數(shù),而帶有 ~ 前綴的是未優(yōu)化的。如果是否優(yōu)化對我們的分析不重要,我們可以點擊 Merge 按鈕把它們合并。這時圖像會變成這樣:

從開始看,我們可以發(fā)現(xiàn)出問題的代碼在 util.js 里。這個過慢的函數(shù)也是一個 event handler:觸發(fā)這個函數(shù)的來源是Node核心里的 events 模塊,而 server.on 是event handler匿名函數(shù)的一個后備名稱。我們可以看到這個代碼跟實際處理本次request請求的代碼并不在同一個 Tick 當中(譯者注: 如果在同一個Tick就會用一個堆棧圖豎向堆疊起來)。如果跟request處理在同一個 Tick中,那堆棧中應該是Node的 http 模塊、net和stream模塊

如果你展開其他的更小的塊你會看到這些Http的Node核心函數(shù)。比如嘗試下右上角的search,搜索關鍵詞 send(restify和http內部方法都有send方法)。然后你可以發(fā)現(xiàn)他們在火焰圖的右邊(函數(shù)按字母排序)(譯者注:右側藍色高亮的區(qū)域)

可以看到實際的 HTTP 處理塊占用時間相對較少。

我們可以點擊一個高亮的青色塊來展開,看到里面 http_outgoing.js 文件的 writeHead、write函數(shù)(Node核心http庫中的一部分)

我們可以點擊 all stack 返回到主要視圖。

這里的關鍵點是,盡管 server.on 函數(shù)跟實際 request處理代碼不在一個 Tick中,它依然能通過延遲其他正在執(zhí)行的代碼來影響了server的性能。

Debuging 調試

我們現(xiàn)在從火焰圖知道了問題函數(shù)在 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指令被展示為最熱的區(qū)域堆棧,并且花費了最多的時間。 JSON.stringify 方法直接調用了 C++代碼,這就是為什么我們看不到JavaScript 函數(shù)。在加密這里, createHashupdate 這樣的函數(shù)都在數(shù)據(jù)中,而他們要么內聯(lián)(合并并消失在merge視圖)要么占用時間太小無法展示。

一旦我們開始推理etagger函數(shù)中的代碼,很快就會發(fā)現(xiàn)它的設計很糟糕。為什么我們要從函數(shù)上下文中獲取服務器實例?所有這些hash計算都是必要的嗎?在實際場景中也沒有If-None-Match頭支持,如果用if-none-match這將減輕某些真實場景中的一些負載,因為客戶端會發(fā)出頭請求來確定資源的新鮮度。

讓我們先忽略所有這些問題,先驗證一下 server.on 中的代碼是否是導致問題的原因。我們可以把 server.on 里面的代碼做成空函數(shù)然后生成一個新的火焰圖。

現(xiàn)在 etagger 函數(shù)變成這樣:

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()
  }
}

現(xiàn)在 server.on 的事件監(jiān)聽函數(shù)是個以空函數(shù) no-op. 讓我們再次執(zhí)行 clinic flame:

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

會生成如下的火焰圖:

這看起來好一些,我們會看到每秒吞吐量有所增長。但是為什么 event emit 的代碼這么紅? 我們期望的是此時 HTTP 處理要占用最多的CPU時間,畢竟 server.on 里面已經什么都沒做了。

這種類型的瓶頸通常因為一個函數(shù)調用超出了一定期望的程度。

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

require("events").defaultMaxListeners = Infinity

讓我們移除掉這句代碼,然后啟動我們的應用,帶上 --trace-warnings flag標記。

node --trace-warnings index.js

如果我們在下一個teminal中執(zhí)行壓測:

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 函數(shù)綁到了 server上。

讓我們看下 attachAfterEvent 函數(shù):

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

我們發(fā)現(xiàn)條件檢查語句寫錯了! 不應該是 attachAfterEvent ,而是 afterEventAttached. 這意味著每個請求都會往 server 對象上添加一個事件監(jiān)聽,然后每個請求的最后所有的之前綁定上的事件都要觸發(fā)。唉呀媽呀!

優(yōu)化

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

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

讓我們還原 server.on 的代碼(不讓他是空函數(shù)了)然后條件語句中改成正確的 boolean 判斷。現(xiàn)在我們的 etagger 函數(shù)這樣:

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()
  }
}

現(xiàn)在,我們再來執(zhí)行一次 Profile(進程剖析,進程描述)。

node index.js

然后用 autocanno 來profile 它:

autocannon -c100 localhost:3000/seed/v1

我們看到結果顯示有200倍的提升(持續(xù)10秒 100個并發(fā))

平衡開發(fā)成本和潛在的服務器成本也非常重要。我們需要定義我們在優(yōu)化時要走多遠。否則我們很容易將80%的時間投入到20%的性能提高上。項目是否能承受?

在一些場景下,用 低端優(yōu)化 來花費一天提高200倍速度才被認為是合理的。而在某些情況下,我們可能希望不惜一切讓我們的項目盡最大最大最大可能的快。這種抉擇要取決于項目優(yōu)先級。

控制資源支出的一種方法是設定目標。例如,提高10倍,或達到每秒4000次請求?;跇I(yè)務需求的這一種方式最有意義。例如,如果服務器成本超出預算100%,我們可以設定2倍改進的目標

更進一步

如果我們再做一張火焰圖,我們會看到:

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

(譯者注: 在做優(yōu)化之前可能每次都要做這樣的思考:) 通過優(yōu)化我們能獲得哪些額外收益,以及這些改變(包括相關聯(lián)的代碼重構)是否值得?

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

我們看最終終極優(yōu)化(譯者注:終極優(yōu)化指的是作者在后文提到的另外一些方法)后能達到的性能特征(持續(xù)執(zhí)行十秒 http://localhost:3000/seed/v1 --- 100個并發(fā)連接)

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

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

為了實現(xiàn)深度改進,需要使用同樣的技術如:profile分析、生成火焰圖、分析、debug、優(yōu)化。最后完成優(yōu)化后的服務器代碼,可以在這里查看。

最后提高到 800/s 的吞吐量,使用了如下方法:

不要先創(chuàng)建對象然后再序列化,不如創(chuàng)建時就直接創(chuàng)建為字符串。

用一些其他的唯一的東西來標識etag,而不是創(chuàng)建hash。

不要對url進行hash,直接用url當key。

這些更改稍微復雜一些,對代碼庫的破壞性稍大一些,并使etagger中間件的靈活性稍微降低,因為它會給路由帶來負擔以提供Etag值。但它在執(zhí)行Profile的機器上每秒可多增加3000個請求。

讓我們看看最終優(yōu)化后的火焰圖:

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

防止性能問題

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

在開發(fā)期間使用性能工具作為非正式檢查點可以避免把性能問題帶入生產環(huán)境。建議將AutoCannon和Clinic(或其他類似的工具)作為日常開發(fā)工具的一部分。

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

在選擇一些廣泛流行的類庫時要多加留意---尤其是留意日志。 在開發(fā)者修復issue的時候,他們可能會在代碼中添加一些日志輸出來幫助他們在未來debug問題。如果她用了一個性能差勁的 logger 組件,這可能會像 溫水煮青蛙 一樣隨著時間的推移扼殺性能。pino 日志組件是一個 Node.js 中可以用的速度最快的JSON換行日志組件。

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

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/105407.html

相關文章

  • ( & 轉載) 2016 JavaScript 后起之秀

    摘要:在年成為最大贏家,贏得了實現(xiàn)的風暴之戰(zhàn)。和他的競爭者位列第二沒有前端開發(fā)者可以忽視和它的生態(tài)系統(tǒng)。他的殺手級特性是探測功能,通過檢查任何用戶的功能,以直觀的方式讓開發(fā)人員檢查所有端點。 2016 JavaScript 后起之秀 本文轉載自:眾成翻譯譯者:zxhycxq鏈接:http://www.zcfy.cc/article/2410原文:https://risingstars2016...

    darry 評論0 收藏0
  • 】國外優(yōu)秀JavaScript資源推薦

    摘要:原文去年,我寫了一篇關于優(yōu)秀資源之獲取優(yōu)秀資源的博文。在谷歌瀏覽器的團隊中,每天的工作是整天修補并了解哪些是可行的,哪些是沒有用的。你需要真正利用在中的特性,不用想就知道你將得到很多來源于各種寫作者,包括谷歌瀏覽器團隊在內的資源。 原文:http://code.tutsplus.com/articles/resources-for-staying-on-top-of-javascrip...

    ?xiaoxiao, 評論0 收藏0
  • Webpack 4.0 發(fā)布:有哪些新特性?(

    摘要:有哪些新特性有哪些改進學著使用這個新版本,來構建更快的應用吧。繼版本之后,花了將近八個月的時間來發(fā)布。的創(chuàng)始人之一,,建議用戶使用,以便使用最優(yōu)的性能,是因為源代碼使用了新特性。全新的插件系統(tǒng)配備了全新整改的插件系統(tǒng)。 本文原文地址:https://auth0.com/blog/webpac...第一次翻譯,不當之處,歡迎指正 官方已經發(fā)布了Webpack 4.0。有哪些新特性?有哪些...

    HitenDev 評論0 收藏0
  • [] Node.js 架構概覽

    摘要:文件系統(tǒng)請求和相關請求都會放進這個線程池處理其他的請求,如網(wǎng)絡平臺特性相關的請求會分發(fā)給相應的系統(tǒng)處理單元參見設計概覽。 譯者按:在 Medium 上看到這篇文章,行文脈絡清晰,闡述簡明利落,果斷點下翻譯按鈕。第一小節(jié)背景鋪陳略啰嗦,可以略過。剛開始我給這部分留了個 blah blah blah 直接翻后面的,翻完之后回頭看,考慮完整性才把第一節(jié)給補上。接下來的內容干貨滿滿,相信對 N...

    antyiwei 評論0 收藏0
  • 「真?全棧之路」Web前端開發(fā)后端指南

    前言 在若干次前的一場面試,面試官看我做過python爬蟲/后端 的工作,順帶問了我些后端相關的問題:你覺得什么是后端? 送命題。當時腦瓦特了,答曰:邏輯處理和數(shù)據(jù)增刪改查。。。 showImg(https://user-gold-cdn.xitu.io/2019/4/24/16a4ed4fc8c18078); 當場被懟得體無完膚,羞愧難當。事后再反思這問題,結合資料總結了一下。發(fā)現(xiàn)自己學過的Re...

    chuyao 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<