[譯] Node.js 之戰: 如何在生產環境中調試錯誤

Node.js 之戰: 在生產環境中調試錯誤

在這篇文章,這篇文章講述了 Netflix、RisingStack 和 nearForm 在生產環境中遇到 Node.js 錯誤的故事 - 所以你能夠此爲鑑,避免犯上一樣的錯誤。同時你將會學到如何調試 Node.js 的錯誤。javascript

感謝來自 Netflix 的 Yunong Xiao、來自 Strongloop 的 NearForm 和來自 Shubhra Kar 的 Matteo Collina 對這篇文章的看法與幫助。php

過去4年裏,咱們在 RisingStack 的生產環境中運行 Node 應用,積累了許多相關經驗 - 感謝 Node.js 諮詢、學習和開發 的業務支持。html

Netflix 和 nearForm 的 Node 開發團隊都同樣,咱們都有把調試過程記錄下來的習慣,所以整個開發團隊 (如今是全世界的開發團隊) 均可以從咱們的錯誤中學習。 前端

Netflix 與 Node 調試: 瞭解你的依賴庫

讓咱們慢慢閱讀咱們的朋友 Yunong Xiao 在 Netflix 發生的故事。java

Netflix 的開發團隊發現他們的應用的響應時間在逐漸變長 - 他們部分終端的延遲每小時增長 10 ms。 node

同時,CPU 使用率的上升也反映了問題的存在。react

Netflix debugging Nodejs in production with the Request latency graph

不一樣時間段請求的傳輸時間 - 圖片來源: Netflixandroid

一開始,他們調查是不是 request handler 形成其響應時間變長。 ios

在隔離測試後,他們發現 request handler 的響應時間穩定在 1 ms 左右。git

因此問題並非這個,他們開始懷疑到底層,是否是棧出現了問題。

接下來 Yunong 和 Netflix 開發團隊的嘗試是這個 CPU 火焰圖 和 Linux 性能事件

Flame graph of Netflix Nodejs slowdown

火焰圖反映了 Netflix 的響應速度正在變慢 - 圖片來源: Netflix

你能夠從火焰圖中看到的東西是

  • 它有一些很高的棧 (這表明有許多函數被調用)
  • 而且一些矩形很寬 (表明咱們在這些函數中耗費了一些時間)

通過深刻調查,開發團隊發現 Express 的 router.handlerouter.handle.next 有許多引用。

Express.js 的源代碼揭示了一系列有趣的事情:

  • 全部終端的 Route handlers 都儲存在一個全局數組中。
  • Express.js 遞歸地遍歷並喚醒全部 handlers 直到它找到合適的 route handler。

在揭示謎題的解決方案前,咱們須要知道更多的細節:

Netflix 的底層代碼包含了每 6 分鐘運行的定時代碼,從拓展資源中抓取新的路由配置信息,更新應用的 route handlers 從而響應改變的信息。

這些是經過刪除並添加新的 handlers 來實現的。意外的是,同時它再一次添加了相同的靜態 handler - 甚至是之前的 API route handlers。這形成的結果是,響應時間額外增長了 10 ms。

從 Netflix 的錯誤中獲取的教訓

  • 必定要了解你的依賴庫 - 首先,你必須在生產環境中使用它們以前,完全地瞭解它們。
  • 可觀察性是關鍵 - 火焰圖幫助 Netflix 工程團隊解決了問題。

從這裏閱讀整個故事: 火焰圖中的 Node.js

當你最須要幫助時候的專家指引

商業化 Node.js,由 RisingStack 提供

瞭解更多

RisingStack CTO: "加密是要花時間的"

你可能已經聽過咱們的故事 拆分單體式應用的故事,咱們的 CTO Peter Marton 把 Trace (咱們的 Node.js 監控系統) 分離成多個微服務模塊。

咱們如今討論的錯誤是 Trace 開發時的響應速度變慢:

做爲一個在 PaaS 運行的 早期 Trace 版本,它經過公共雲來與咱們的其餘服務通訊。

爲了確保咱們的請求是完整的,咱們決定對全部請求進行簽名。爲了實現這個,咱們看了 Joyent 的 HTTP signing library。很棒的是,request 這一模塊支持開箱即用的HTTP簽名。

解決方案代價不只很大,並且會對咱們的響應速度形成很差的影響。

network delay in nodejs request visualized by trace

網絡延遲增長了咱們的響應時間 - 圖片來源: Trace

從圖中可看到,所給定的終端響應速度爲 180 ms,然而對於整體來講,單獨兩個服務的網絡延遲只是 100 ms

一開始,咱們 用 Kubernetes 轉移 PaaS provider。咱們但願響應速度會快一點,這樣內部網絡就會平衡。

咱們的方法奏效了 - 終端的響應速度提升了。

然而,咱們想要更好的結果 - 大幅度下降 CPU 的使用率。下一步是分析 CPU 的使用狀況,就像 Netflix 的人們作的同樣:

crypto sign function taking up cpu time

從截圖能夠看出,crypto.sign 函數消耗的 CPU 時間最多,每次請求花費 10 ms。爲了解決這個問題,你有兩種選擇:

  • 若是你在可信任的環境中運行應用,你能夠去除請求籤名,
  • 若是你在不可信的環境中運行,你能夠升級你的機器讓它擁有更強大的 CPU。

從 Peter Marton 中獲取的教訓

  • 服務之間的終端信息傳輸會對用戶體驗有巨大的影響 - 儘量的平衡內部網絡。
  • 加密可能會消耗大量時間

nearForm: 不要堵塞 Node.js 的事件循環

React 如今很流行。開發者在前端和後端都會使用它,甚至他們更進一步用它來構建同構的 JavaScript 應用。

然而,渲染 React 頁面會讓 CPU 有挺大的負擔,當繪製複雜的 React 內容時會受到 CPU 限制。

當你的 Node.js 正在進行繪製,它會堵塞事件循環,由於它的行爲都是基於同步的。

結果就是,服務器可能會毫無反應 - 當請求堆積起來,會把全部的負擔都堆在 CPU 上。

更糟的是即便請求端已經關閉,請求仍然會被處理 - 仍然會對 Node.js 應用形成負擔,nearForm 對此有解釋 Matteo Collina

不只是 React,大多數字符串操做也會這樣。 若是你在構建 JSON REST APIs,你應該花心思在 JSON.parseJSON.stringify

Strongloop(如今是 Joyent) 的 Shubhra Kar 對此解釋是,解析和轉化成 JSON 字符串的等消耗巨大的操做也會消耗大量時間 (同時在這期間會堵塞事件循環)

functionrequestHandler(req, res) {  
  const body = req.rawBody
  let parsedBody
  try {
    parsedBody = JSON.parse(body)
  }
  catch(e) {
     res.end(newError('Error parsing the body'))
  }
  res.end('Record successfully received')
}複製代碼

簡易的 request handler

這個例子展現了一個簡易的 request handler,用來解析 body。對於內容很少的狀況下,它運行的挺好 - 然而,若是 JSON 的大小要以兆來描述的話,可能會花費數秒的時間來執行 而不是在毫秒時間內執行。同理 JSON.stringify 也同樣。

爲了緩解這個問題,首先你要了解它們。爲此,你能夠用 Matteo 的 loopbench 模塊,或者 Trace 的事件循環度量功能。

經過 loopbench,若是請求沒有被實現,你能夠返回狀態碼 503 給負載平衡器。爲了啓用這項功能,你要使用選項 instance.overLimit。這樣 ELB 或者 NGINX 能夠在不一樣的後端中重試,這樣請求有可能會被處理。

一旦你瞭解這個問題並理解它,你就能開始修正它 - 你能夠經過平衡 Node.js 流或者改變正在使用的架構來進行修正。

從 nearForm 中獲取的教訓

  • 總要留心對 CPU 負擔大的操做 - 這類的操做越多,在你的事件循環裏對 CPU 形成的壓力越大。
  • 字符串操做會對 CPU 形成巨大負擔

在生產環境中調試 Node.js 錯誤

我但願 Netflix、RisingStack 和 nearForm 的例子會對你在生產環境中調試 Node.js 應用有幫助。

若是你想要了解更多,我建議看下最近這些文章,它們會加深你的 Node 知識:

若有任何疑問,請留下評論讓咱們知道!


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃

相關文章
相關標籤/搜索