追蹤 Node.js 中的高記憶體使用率
在本文中,我將分享我在 Node.js 中追蹤和修復高記憶體使用率的方法。
內容
- 上下文
-
方法
- 理解程式碼
- 單獨複製問題
- 從暫存服務擷取設定檔
- 驗證修復
- 結果
- 結論
情境
最近我收到了一張標題為「修復庫 x 中的記憶體洩漏問題」的票證。該描述包括一個 Datadog 儀表板,其中顯示了十幾個因記憶體使用率過高並最終因 OOM(記憶體不足)錯誤而崩潰的服務,並且它們都有共同的 x 庫。
我最近才接觸到程式碼庫(不到 2 週),這使得這項任務充滿挑戰性,也值得分享。
我開始使用兩個訊息:
- 有一個所有服務都使用的函式庫導致記憶體使用率很高,它涉及到redis(庫的名稱中包含redis)。
- 受影響的服務清單。
以下是連結到票證的儀表板:
服務在 Kubernetes 上運行,很明顯,服務會隨著時間的推移積累內存,直到達到內存限制、崩潰(回收內存)並重新啟動。
方法
在本節中,我將分享我如何處理手頭上的任務,找出高記憶體使用率的罪魁禍首並隨後修復它。
理解程式碼
由於我對程式碼庫相當陌生,我首先想了解程式碼、相關函式庫的作用以及它應該如何使用,希望透過這個過程可以更容易地識別問題。不幸的是,沒有適當的文檔,但透過閱讀程式碼和搜尋服務如何利用該庫,我能夠理解它的要點。它是一個圍繞 redis 流的庫,並為事件生成和消費提供方便的介面。花了一天半的時間閱讀程式碼,由於程式碼結構和複雜性(許多我不熟悉的類別繼承和rxjs),我無法掌握所有細節以及資料如何流動。
因此我決定暫停閱讀,並在觀察程式碼運行情況並收集遙測資料的同時嘗試發現問題。
單獨複製該問題
由於沒有可用的分析資料(例如連續分析)可以幫助我進一步調查,因此我決定在本地複製該問題並嘗試捕獲記憶體配置檔案。
我發現了幾種在 Node.js 中捕獲記憶體設定檔的方法:
- 使用堆快照
- 使用堆分析器
- JavaScript 效能分析
- Clinic.js
由於不知道去哪裡尋找,我決定運行我認為是庫中最「資料密集」的部分,即 redis 流生產者和消費者。我建立了兩個簡單的服務,它們可以產生和使用來自 redis 流的數據,然後我繼續捕獲記憶體配置文件並比較一段時間內的結果。不幸的是,在對服務產生負載並比較配置文件幾個小時後,我無法發現這兩個服務中任何一個服務的記憶體消耗有任何差異,一切看起來都很正常。該庫公開了一系列不同的介面以及與 Redis 流交互的方式。我很清楚,複製該問題比我預期的要複雜得多,尤其是在我對實際服務的特定領域知識有限的情況下。
所以問題是,如何找到合適的時機和條件來捕獲記憶體洩漏?
從暫存服務捕獲設定檔
如前所述,捕獲記憶體設定檔的最簡單、最方便的方法是對受影響的實際服務進行連續分析,但我沒有這個選項。我開始研究如何至少利用我們的暫存服務(它們面臨同樣的高記憶體消耗),這將使我無需額外的努力即可捕獲所需的資料。
我開始尋找一種將 Chrome DevTools 連接到其中一個正在運行的 Pod 並隨著時間的推移捕獲堆快照的方法。我知道記憶體洩漏發生在暫存階段,因此,如果我能夠捕獲該數據,我希望能夠至少發現一些熱點。令我驚訝的是,有一種方法可以做到這一點。
執行此操作的過程
- 透過向 pod 上的節點進程發送 SIGUSR1 訊號來啟用 pod 上的 Node.js 偵錯器。
kubectl exec -it <nodejs-pod-name> -- kill -SIGUSR1 <node-process-id>
更多關於 Signal Events 中的 Node.js 訊號
如果成功,您應該會看到來自服務的日誌:
Debugger listening on ws://127.0.0.1:9229/.... For help, see: https://nodejs.org/en/docs/inspector
- 透過運行在本地公開調試器正在偵聽的端口
kubectl port-forward <nodejs-pod-name> 9229
- 將 Chrome Devtools 連接到您在先前步驟中啟用的偵錯器。造訪 chrome://inspect/,您應該在目標清單中看到您的 Node.js 進程:
如果沒有,請確保您的目標發現設定正確設定
現在您可以開始捕捉逾時快照(時間段取決於發生記憶體洩漏所需的時間)並進行比較。 Chrome DevTools 提供了一種非常方便的方法來做到這一點。
您可以在記錄堆快照中找到有關記憶體快照和 Chrome 開發工具的更多資訊
建立快照時,主執行緒中的所有其他工作都會停止。根據堆內容,甚至可能需要一分多鐘的時間。快照內建在記憶體中,因此它可以使堆大小加倍,從而導致填滿整個內存,然後使應用程式崩潰。
如果您要在生產中取得堆疊快照,請確保從中取得快照的進程可以崩潰,而不會影響應用程式的可用性。
來自 Node.js 文件
回到我的例子,選擇兩個快照進行比較並按增量排序,我得到了您在下面看到的內容。
我們可以看到最大的正增量發生在字串建構子上,這意味著該服務在兩個快照之間創建了許多字串,但它們仍在使用中。現在的問題是它們是在哪裡創建的以及誰在引用它們。幸運的是,捕獲的快照包含了這些信息,也稱為 Retainers。
在深入研究快照和永不縮小的字串列表時,我注意到一種類似於 id 的字串模式。單擊它們,我可以看到引用它們的鏈對象 - 又稱保留器。這是一個名為 sendEvents 的數組,其類別名稱是我可以從庫程式碼中識別出來的。哎呀,我們找到了罪魁禍首,一個不斷增長的 id 列表,到目前為止我認為這些列表從未被發布過。我超時拍攝了一堆快照,這是唯一一個不斷重新出現為具有較大正增量的熱點的地方。
驗證修復情況
有了這些訊息,我不需要嘗試完全理解程式碼,而是需要關注陣列的用途、何時填充和何時清除。在一個地方,程式碼將項目推送到數組,而在另一個地方,程式碼將項目彈出,這縮小了修復的範圍。
可以安全地假設數組在應該清空的時候沒有被清空。跳過程式碼的細節,基本上發生的事情是這樣的:
- 該程式庫公開了用於消費、產生事件或產生和消費事件的介面。
- 當它既消耗又產生事件時,它需要追蹤進程本身產生的事件,以便跳過它們而不重新消耗它們。 sendEvents 在生成時被填充,並在嘗試使用時被清除,它會跳過訊息。
你能看出這是怎麼回事嗎? ?當服務僅使用該程式庫來產生事件時,sentEvents 仍會填入所有事件,但沒有程式碼路徑(使用者)用於清除它。
我修補了程式碼以僅追蹤生產者、消費者模式上的事件並部署到登台。即使存在暫存負載,很明顯該補丁也有助於減少高記憶體使用率,並且沒有引入任何回歸。
結果
當修補程式部署到生產環境時,記憶體使用量大幅減少,服務的可靠性得到提高(不再出現 OOM)。
一個很好的副作用是處理相同流量所需的 Pod 數量減少了 50%。
結論
對我來說,這是一個很好的學習機會,可以追蹤 Node.js 中的記憶體問題並進一步熟悉可用的工具。
我認為最好不要詳細討論每個工具的細節,因為這值得單獨發表一篇文章,但我希望這對於任何有興趣了解更多有關此主題或面臨類似問題的人來說是一個很好的起點。
以上是追蹤 Node.js 中的高記憶體使用率的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。
