天天看點

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

<b>本文講的是[譯] Node.js 之戰: 如何在生産環境中調試錯誤,</b>

在這篇文章,這篇文章講述了 Netflix、RisingStack 和 nearForm 在生産環境中遇到 Node.js 錯誤的故事 - 是以你可以此為鑒,避免犯上同樣的錯誤。同時你将會學到如何調試 Node.js 的錯誤。

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

Netflix 和 nearForm 的 Node 開發團隊都一樣,我們都有把調試過程記錄下來的習慣,是以整個開發團隊 (現在是全世界的開發團隊) 都可以從我們的錯誤中學習。

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

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

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

Netflix debugging Nodejs in production with the Request latency graph

不同時間段請求的傳輸時間 - 圖檔來源: Netflix

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

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

是以問題并不是這個,他們開始懷疑到底層,是不是棧出現了問題。

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

Flame graph of Netflix Nodejs slowdown

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

你可以從火焰圖中看到的東西是

它有一些很高的棧 (這代表有許多函數被調用)

并且一些矩形很寬 (代表我們在這些函數中耗費了一些時間)

經過深入調查,開發團隊發現 Express 的 <code>router.handle</code> 和 <code>router.handle.next</code> 有許多引用。

Express.js 的源代碼揭示了一系列有趣的事情: 所有終端的 Route handlers 都儲存在一個全局數組中。 Express.js 遞歸地周遊并喚醒所有 handlers 直到它找到合适的 route handler。

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

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

這些是通過删除并添加新的 handlers 來實作的。意外的是,同時它再一次添加了相同的靜态 handler - 甚至是以前的 API route handlers。這造成的結果是,響應時間額外增加了 10 ms。

一定要了解你的依賴庫 - 首先,你必須在生産環境中使用它們之前,徹底地了解它們。

可觀察性是關鍵 - 火焰圖幫助 Netflix 工程團隊解決了問題。

<a href="https://link.juejin.im/?target=https%3A%2F%2Frisingstack.com%2Fnodejs-support%3Futm_source%3Drsblog%26amp%3Butm_medium%3Droadblock-new%26amp%3Butm_campaign%3Dtrace%26amp%3Butm_content%3D%2Fnode-js-war-stories-solving-issues-in-production-2%2F" target="_blank">了解更多</a>

我們現在讨論的錯誤是 Trace 開發時的響應速度變慢:

作為一個在 PaaS 運作的 早期 Trace 版本,它通過公共雲來與我們的其他服務通信。

解決方案代價不僅很大,而且會對我們的響應速度造成不好的影響。

network delay in nodejs request visualized by trace

網絡延遲增加了我們的響應時間 - 圖檔來源: Trace

從圖中可看到,所給定的終端響應速度為 180 ms,然而對于總體來說,單獨兩個服務的網絡延遲隻是 100 ms。

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

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

crypto sign function taking up cpu time

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

如果你在可信任的環境中運作應用,你可以去除請求簽名,

如果你在不可信的環境中運作,你可以更新你的機器讓它擁有更強大的 CPU。

服務之間的終端資訊傳輸會對使用者體驗有巨大的影響 - 盡可能的平衡内部網絡。

加密可能會消耗大量時間。

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

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

當你的 Node.js 正在進行繪制,它會堵塞事件循環,因為它的行為都是基于同步的。

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

不僅是 React,大多數字元串操作也會這樣。 如果你在建構 JSON REST APIs,你應該花心思在 <code>JSON.parse</code> 和 <code>JSON.stringify</code>。

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

簡易的 request handler

這個例子展示了一個簡易的 request handler,用來解析 body。對于内容不多的情況下,它運作的挺好 - 然而,如果 JSON 的大小要以兆來描述的話,可能會花費數秒的時間來執行 而不是在毫秒時間内執行。同理 <code>JSON.stringify</code> 也一樣。

通過 <code>loopbench</code>,如果請求沒有被實作,你可以傳回狀态碼 503 給負載平衡器。為了啟用這項功能,你要使用選項 <code>instance.overLimit</code>。這樣 ELB 或者 NGINX 可以在不同的後端中重試,這樣請求有可能會被處理。

一旦你了解這個問題并了解它,你就能開始修正它 - 你可以通過平衡 Node.js 流或者改變正在使用的架構來進行修正。

總要留心對 CPU 負擔大的操作 - 這類的操作越多,在你的事件循環裡對 CPU 造成的壓力越大。

字元串操作會對 CPU 造成巨大負擔

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

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

<a href="https://link.juejin.im/?target=https%3A%2F%2Fblog.risingstack.com%2Fcase-study-node-js-memory-leak-in-ghost%2F" target="_blank">案例學習:在 Ghost 中查找 Node.js 記憶體洩漏</a>

<a href="https://link.juejin.im/?target=https%3A%2F%2Fblog.risingstack.com%2Fnode-js-at-scale-understanding-node-js-event-loop%2F" target="_blank">了解 Node.js 事件循環</a>

<a href="https://link.juejin.im/?target=https%3A%2F%2Fblog.risingstack.com%2Fnode-js-at-scale-node-js-garbage-collection%2F" target="_blank">解釋 Node.js 垃圾回收</a>

<a href="https://link.juejin.im/?target=https%3A%2F%2Fblog.risingstack.com%2Fnode-js-async-best-practices-avoiding-callback-hell-node-js-at-scale%2F" target="_blank">Node.js 異步最佳實踐和如何避免回調地獄</a>

<a href="https://link.juejin.im/?target=https%3A%2F%2Fblog.risingstack.com%2Fevent-sourcing-with-examples-node-js-at-scale%2F" target="_blank">Node.js 的事件溯源示範</a>

<a href="https://link.juejin.im/?target=https%3A%2F%2Fblog.risingstack.com%2Fgetting-node-js-testing-and-tdd-right-node-js-at-scale%2F" target="_blank">正确地開始 Node.js 測試和 TDD</a>

<a href="https://link.juejin.im/?target=https%3A%2F%2Fblog.risingstack.com%2F10-best-practices-for-writing-node-js-rest-apis%2F" target="_blank">10個 Node.js REST APIs 最佳實踐</a>

<a href="https://link.juejin.im/?target=https%3A%2F%2Fblog.risingstack.com%2Fend-to-end-testing-with-nightwatch-js-node-js-at-scale%2F" target="_blank">使用 Nightwatch.js 對 Node.js 進行端到端測試</a>

<a href="https://link.juejin.im/?target=https%3A%2F%2Fblog.risingstack.com%2Fmonitoring-nodejs-applications-nodejs-at-scale%2F" target="_blank">監測 Node.js 應用的最終指南</a>

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

<b></b>

<b>原文釋出時間為:2017年4月28日</b>

<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>

繼續閱讀