- 原文地址:Node.js War Stories: Debugging Issues in Production
- 原文做者:Gergely Nemeth
- 譯文出自:掘金翻譯計劃
- 譯者:mnikn
- 校對者:lsvih、Aladdin-ADD
在這篇文章,這篇文章講述了 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 開發團隊都同樣,咱們都有把調試過程記錄下來的習慣,所以整個開發團隊 (如今是全世界的開發團隊) 均可以從咱們的錯誤中學習。 前端
讓咱們慢慢閱讀咱們的朋友 Yunong Xiao 在 Netflix 發生的故事。java
Netflix 的開發團隊發現他們的應用的響應時間在逐漸變長 - 他們部分終端的延遲每小時增長 10 ms。 node
同時,CPU 使用率的上升也反映了問題的存在。react
不一樣時間段請求的傳輸時間 - 圖片來源: Netflixandroid
一開始,他們調查是不是 request handler 形成其響應時間變長。 ios
在隔離測試後,他們發現 request handler 的響應時間穩定在 1 ms 左右。git
因此問題並非這個,他們開始懷疑到底層,是否是棧出現了問題。
接下來 Yunong 和 Netflix 開發團隊的嘗試是這個 CPU 火焰圖 和 Linux 性能事件。
火焰圖反映了 Netflix 的響應速度正在變慢 - 圖片來源: Netflix
你能夠從火焰圖中看到的東西是
通過深刻調查,開發團隊發現 Express 的 router.handle
和 router.handle.next
有許多引用。
Express.js 的源代碼揭示了一系列有趣的事情:
- 全部終端的 Route handlers 都儲存在一個全局數組中。
- Express.js 遞歸地遍歷並喚醒全部 handlers 直到它找到合適的 route handler。
在揭示謎題的解決方案前,咱們須要知道更多的細節:
Netflix 的底層代碼包含了每 6 分鐘運行的定時代碼,從拓展資源中抓取新的路由配置信息,更新應用的 route handlers 從而響應改變的信息。
這些是經過刪除並添加新的 handlers 來實現的。意外的是,同時它再一次添加了相同的靜態 handler - 甚至是之前的 API route handlers。這形成的結果是,響應時間額外增長了 10 ms。
從這裏閱讀整個故事: 火焰圖中的 Node.js。
你可能已經聽過咱們的故事 拆分單體式應用的故事,咱們的 CTO Peter Marton 把 Trace (咱們的 Node.js 監控系統) 分離成多個微服務模塊。
咱們如今討論的錯誤是 Trace 開發時的響應速度變慢:
做爲一個在 PaaS 運行的 早期 Trace 版本,它經過公共雲來與咱們的其餘服務通訊。
爲了確保咱們的請求是完整的,咱們決定對全部請求進行簽名。爲了實現這個,咱們看了 Joyent 的 HTTP signing library。很棒的是,request 這一模塊支持開箱即用的HTTP簽名。
解決方案代價不只很大,並且會對咱們的響應速度形成很差的影響。
網絡延遲增長了咱們的響應時間 - 圖片來源: Trace
從圖中可看到,所給定的終端響應速度爲 180 ms,然而對於整體來講,單獨兩個服務的網絡延遲只是 100 ms。
一開始,咱們 用 Kubernetes 轉移 PaaS provider。咱們但願響應速度會快一點,這樣內部網絡就會平衡。
咱們的方法奏效了 - 終端的響應速度提升了。
然而,咱們想要更好的結果 - 大幅度下降 CPU 的使用率。下一步是分析 CPU 的使用狀況,就像 Netflix 的人們作的同樣:
從截圖能夠看出,crypto.sign
函數消耗的 CPU 時間最多,每次請求花費 10 ms。爲了解決這個問題,你有兩種選擇:
React 如今很流行。開發者在前端和後端都會使用它,甚至他們更進一步用它來構建同構的 JavaScript 應用。
然而,渲染 React 頁面會讓 CPU 有挺大的負擔,當繪製複雜的 React 內容時會受到 CPU 限制。
當你的 Node.js 正在進行繪製,它會堵塞事件循環,由於它的行爲都是基於同步的。
結果就是,服務器可能會毫無反應 - 當請求堆積起來,會把全部的負擔都堆在 CPU 上。
更糟的是即便請求端已經關閉,請求仍然會被處理 - 仍然會對 Node.js 應用形成負擔,nearForm 對此有解釋 Matteo Collina。
不只是 React,大多數字符串操做也會這樣。 若是你在構建 JSON REST APIs,你應該花心思在 JSON.parse
和 JSON.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 流或者改變正在使用的架構來進行修正。
我但願 Netflix、RisingStack 和 nearForm 的例子會對你在生產環境中調試 Node.js 應用有幫助。
若是你想要了解更多,我建議看下最近這些文章,它們會加深你的 Node 知識:
若有任何疑問,請留下評論讓咱們知道!
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃。