1. 程式人生 > >Node.js 應用故障排查手冊 —— Node.js 效能平臺使用指南

Node.js 應用故障排查手冊 —— Node.js 效能平臺使用指南

開發十年,就只剩下這套架構體系了! >>>   

楔子

前一節中我們藉助於 Chrome devtools 實現了對線上 Node.js 應用的 CPU/Memory 問題的排查定位,但是在實際生產實踐中,大家會發現 Chrome devtools 更加偏向本地開發模式,因為顯然 Chrome devtools 不會負責去生成分析問題所需要的 Dump 檔案,這意味著開發者還得額外在線上專案中設定好 

v8-profiler 和 heapdump 這樣的工具,並且通過額外實現的服務來能夠去對線上執行的專案進行實時的狀態匯出。

加上實際上預備章中除了 CPU/Memory 的問題,我們還會遇到一些需要分析錯誤日誌、磁碟和核心轉儲檔案等才能定位問題的狀況,因此在這些場景下,僅僅靠 Chrome devtools 顯然會有一些力不從心。正是為了解決廣大 Node.js 開發者的這些痛點,我們在這裡推薦大家在使用 Node.js 效能平臺,即原來的 AliNode,它已經在阿里巴巴集團內部承載了幾乎所有的 Node.js 應用線上執行監控和問題排查,因此大家可以放心在生產環境部署使用。

本節將從 Node.js 效能平臺 的設計架構、核心能力以及最佳實踐等角度,幫助開發者更好地使用這一工具來解決前面提到的異常指標分析和線上 Node.js 應用故障定位。

本書首發在 Github,倉庫地址:https://github.com/aliyun-node/Node.js-Troubleshooting-Guide,雲棲社群會同步更新。

架構

Node.js 效能平臺其實簡單的說由三部分組成:雲控制檯 + AliNode runtime + Agenthub,如下圖所示:

具體的部署步驟可以檢視官方文件:自助式部署 Runtime

。藉助於 Node.js 效能平臺的整套解決方案,我們可以很方便地實現預備章節中提到的絕大部分異常指標的告警分析的能力。在生產實踐過程中,實際上在筆者看來,Node.js 效能平臺解決方案其實僅僅是提供了三個最核心卻也是最有效的能力:

  • 異常指標告警,即預備節中一些異常指標出現異常時能通過簡訊/釘釘通知到開發者
  • 匯出線上 Node.js 應用狀態,包括但不限於即前面 Chrome devtools 一節中提到的 CPU/Memory 狀態匯出
  • 線上分析結果和更好的 UI 展示,定製化解析應用匯出狀態和展示,更符合國內開發者習慣

換言之,Node.js 效能平臺作為一個產品本身功能也在不斷迭代新增修改中,但是以上的三個核心能力一定是第一優先順序保障的,其它邊邊角角的功能則相對來說響應優先順序沒有那麼高。

實際上我們也理解作為使用平臺的開發者希望能在一個地方看到 Node.js 線上應用從底層到業務層的所有細節,然而我個人感覺不同的工具都有應該有其核心的能力輸出,很多時候不斷做加法容易讓產品本身定位模糊化以及泛而不精,Node.js 效能平臺實際上始終在致力於讓原本線上黑盒的執行時狀態,能更加直觀地反饋給開發者,讓 Node.js 應用的開發者面對一些偏向底層的線上疑難雜症能夠不再無所適從。

最佳實踐

I. 配置合適的告警

線上應用的告警實際上是一種自我發現問題的保護機制,如果沒有告警能力,那每次都會等到問題暴露到使用者側導致其反饋才能發現問題,這顯然對使用者體驗非常的不友好。

因此部署完成一個專案後,開發者首先需要去配置合適的告警,而在我們的生產實踐中,線上問題通過錯誤日誌、Node.js 程序 CPU/Memory 的分析、核心轉儲(Core dump)的分析以及磁碟分析能夠得出結論,因此我們需要的基本的告警策略也是源自以上五個部分。幸運的是平臺已經給我們預設好了這些告警,大家只需要選擇一下即可完整這裡的告警配置,如下圖所示:

在 Node.js 效能平臺 的告警頁面上有 快速新增規則,點開選中後會自動生成告警規則的閾值表示式模板和報警說明模板,我們可以按照專案實際監控需求進行修改,比如想要對 Node.js 程序的堆記憶體進行監控,可以選中 Memory 預警 選項,如下圖所示:

此時點選 新增報警項 即完整了對程序堆記憶體的告警,並且將出現告警時需要點選 通知設定->新增到聯絡人列表 來新增的聯絡人加入此條規則,如下圖所示:

那麼在例子中的這條預設的規則裡,當我們的 Node.js 程序分配的堆記憶體超過堆上線的 80%(預設 64 位機器上堆上限是 1.4G)時會觸發簡訊通知到配置繫結到此條規則的聯絡人。

實際上快速新增規則列表中給大家提供的是最常見的一些預配置好的告警策略,如果這些尚不能滿足你的需求,更多定製化的自定義的服務告警策略配置方法可以看官方文件 報警設定。並且除了簡訊告警,也支援釘釘機器人推送告警訊息到群,方便群發感知線上 Node.js 應用態勢。

II. 按照告警型別進行分析

按照 I 節中配置完成合適的告警規則後,那麼當收到告警簡訊時就可以按照策略型別進行對應的分析了。本節將按照預備節中比較常見的五大類問題來逐一講解。

a. 磁碟監控

這個是比較好處理的問題,在快速新增的規則裡實際上我們會在伺服器的磁碟使用超過 85% 時進行告警,那麼收到磁碟告警後,可以連線到伺服器,使用如下命令檢視那個目錄佔用比較高:

sudo du -h --max-depth=1 /

找到佔比比較高的目錄和檔案後,看看是否需要備份後刪除來釋放出磁碟空間。

b. 錯誤日誌

收到特定的錯誤日誌告警後,只需要去對應的專案的 Node.js 效能平臺 控制檯找到問題 例項 去檢視其 異常日誌 即可,如下圖所示:

這裡會按照錯誤型別進行規整,大家可以結合展示的錯誤棧資訊來進行對應的問題定位。注意這裡的錯誤日誌檔案需要你在部署 Agenthub 的時候寫入配置檔案,詳細可以參見文件 配置和啟動 Agenthub 一節中的 詳細配置 內容。

c. 程序 CPU 高

終於到了前一節中藉助 v8-profiler 匯出 CPU Profile 檔案再使用 Chrome devtools 進行分析的異常型別了。那麼在 Node.js 效能平臺 的整套解決方案下,我們並不需要額外的去依賴類似 v8-profiler 這樣的第三方庫來實現程序狀態的匯出,與此相對的,當我們收到 Node.js 應用程序的 CPU 超過我們設定的閾值告警時,我們只需要在控制檯對應的 例項 點選 CPU Profile 按鈕即可:

預設會給抓取的程序生成 3 分鐘的 CPU Profile 檔案,等到結束後生成的檔案會顯示在 檔案 頁面:

此時點選 轉儲 即可上傳到雲端以供線上分析展示了,如下圖所示:

這裡可以看到有兩個 分析 按鈕,其實第二個下標帶有 (devtools) 的分析按鈕實際上就是前一節中提到的 Chrome devtools 分析,這裡不再重複講解了,如果有遺忘的同學可以再去回顧下本大章前一節的內容。我們重點看下第一個 AliNode 定製的分析,點選第一個分析按鈕後,可以在新頁面看到如下所示內容:

這裡其實也是火焰圖,但與 Chrome devtools 提供的火焰圖不一樣的地方在於,這裡是將抓取的 3 分鐘內的 JS 函式執行進行了聚合展示出來的火焰圖,在一些存在多次執行同一個函式(可能每次執行非常短)的情況下,聚合後的火焰圖可以很方便的幫助我們找到程式碼的執行瓶頸來進行對應的優化。

值得一提的是,如果你使用的 AliNode runtime 版本在 v3.11.4 或者 v4.2.1 以上(包含這兩個版本)的話,當你的應用出現類死迴圈問題,比如由於異常的使用者引數導致的正則回溯(即執行完這個正則要十幾年,類似於 Node.js 程序發生了死迴圈)這類問題時,可以通過抓取 CPU Profile 檔案來很方便地定位到問題程式碼,詳細資訊有興趣的同學可以看下 Node.js 效能平臺支援死迴圈和正則攻擊定位

d. 記憶體洩漏

與 CPU 高的問題一樣,當我們收到 Node.js 應用程序的堆記憶體佔據堆上限的比率超過我們設定的閾值時,我們也不需要類似 heapdump 這樣的第三方模組來匯出堆快照進行分析,我們還是在控制檯對應的 例項 點選 堆快照 按鈕即可生成對應 Node.js 程序的堆快照:

生成的堆快照檔案同樣會顯示在 檔案 列表頁面,點選 轉儲 將堆快照上傳至雲端以供接下來的分析:

與上面一樣,下標帶有 (devtools) 的分析按鈕還是前一節中提到的 Chrome devtools 分析,這裡還是著重解析下 AliNode 定製的第一個分析按鈕,點選後新頁面如下圖所示:

首先解釋下上面的總覽欄目的內容資訊:

  • 檔案大小: 堆快照檔案本身的大小
  • Shallow Szie 總大小: 回顧下上一節中的內容,GC 根的 Retained Size 大小其實就是堆大小,也等於堆上分配的所有物件的 Shallow Size 總大小,因此這裡其實就是已使用的堆空間
  • 物件個數: 當前堆上分配的 Heap Object 總個數
  • 物件邊個數: 這個稍微抽象一些,假如 Object A.b 指向另一個 Object B,我們則認為表示指向關係的 b 屬性就是一條邊
  • GC Roots 個數: V8 引擎實現的堆分配,其實並不是我們之前為了幫助大家理解簡化的只有一個 GC 根的情況,在實際的執行模型下,堆空間記憶體在許多的 GC 根,這裡是真實的 GC 根的個數

這部分的資訊旨在給大家一個概覽,部分資訊需要深入解讀堆快照才能徹底理解,如果你實在無法理解其中的幾個概覽指標資訊,其實也無傷大雅,因為這並不影響我們定位問題程式碼。

簡單瞭解了概覽資訊的含義後,接著我們來看對於定位 Node.js 應用程式碼段非常重要的資訊,第一個是預設展示的 可疑點 資訊,上圖中的內容表示 @15249 這個物件佔據了堆空間 97.41% 的記憶體,那麼它很可能就是一個洩漏物件,這裡又存在兩種可能:

  • 此物件本身應該被釋放但是卻沒有釋放,造成堆空間佔用如此大
  • 此物件的某些屬性應該被釋放但是卻沒有釋放,造成表象是此物件佔據大量的堆空間

要判斷是哪一種情況,以及追蹤到對應的程式碼段,我們需要點選圖中的 簇檢視 連結進行進一步觀察:

image.png

這裡繼續解釋下什麼是簇檢視,簇檢視實際上是支配樹的一個別名,也就是這個檢視下我們看到的正是前面一節中提到的從可疑洩漏物件出發的支配樹檢視,它的好處是,在這個檢視下,父節點的 Retained Size 可以直接由其子節點的 Retained Size 累加後再加上父節點自身的 Shallow Size 得到,換言之,在這個檢視下我們層層展開即可以看到可疑洩漏物件的記憶體究竟被哪些子節點佔用了。

並且結合前一節的支配樹描述,我們可以知道支配樹下的父子節點關係,並不一定是真正的堆上空間內的物件父子關係,但是對於那些支配樹下父子關係在真正的堆空間內也存在父子節點關係的簇節點,我們將真正的  也用淺紫色標識出來,這部分的  資訊對於我們對映到真正的程式碼段非常有幫助。在這個簡單的例子中,我們可以很清晰的看到可疑洩漏物件 @15249 實際上是下屬的 test-alinode.js 中存在一個 array 變數,其中儲存了四個 45.78 兆的陣列導致的問題,這樣就找到了問題程式碼可以進行後續優化。

而在實際生產環境的堆快照分析下,很多情況下簇檢視下的父子關係在真實的堆空間中並不存在,那麼就不會有這些紫色的邊資訊展示,這時候我們想要知道可疑洩漏物件如何通過 JavaScript 生成的物件間引用關係引用到後面真正佔據掉堆空間的物件(比如上圖中的 40 多兆的 Array 物件),我們可以點選 可疑節點自身的地址連結 :

image.png

這樣就進入到以此物件為起點的堆空間內真正的物件引用關係檢視 Search 檢視

image.png

這個檢視因為反映的是堆空間內各個 Heap Object 之間真正的引用連線關係,因此父物件的 Retained Size 並不能直接由子節點的 Retained Size 累加獲取,如上圖紅框內的內容,顯然這裡的三個子節點 Retained Size 累加已經超過 100%,這也是 Search 檢視和簇檢視很大的不同點。藉助於 Search 檢視,我們可以根據其內反映出來的物件和邊之間的關係來定位可疑洩漏物件具體是在我們的 JavaScript 程式碼中的哪一部分生成。

其實看到這邊,一些讀者應該意識到了這裡的 Search 檢視實際上對應的就是前一節中提到的 Chrome devtools 的 Containment 檢視,只不過這裡的起始點是我們選中的物件本身罷了。

最後就是需要提一下 Retainers 檢視,它和前一節中提到的 Chrome devtools 中解析堆快照展示結果裡面的 Retainers 含義是一致的,它表示物件的父引用關係鏈,我們可以來看下:

image.png

這裡 globa@1279 物件的 clearImmediate 屬性指向 timers.js()@15325,而 timers.js()@15325 的 context 屬性指向了可疑的洩漏物件 system / Context@15249。

在絕大部分的情況下,通過結合 Search 檢視 和 Retainers 檢視 我們可以定位到指定物件在 JavaScript 程式碼中的生成位置,而 簇檢視 下我們又可以比較方便的知道堆空間被哪些物件佔據掉了,那麼綜合這兩部分的資訊,我們就可以實現對線上記憶體洩漏的問題進行分析和程式碼定位了。

 

e. 出現核心轉儲

最後就是收到伺服器生成核心轉儲檔案(Core dump 檔案)的告警了,這表示我們的程序已經出現了預期之外的 Crash,如果你的 Agenthub 配置正常的話,在 檔案 -> Coredump 檔案 頁面會自動將生成的核心轉儲檔案資訊展示出來:

image.png

和之前的步驟類似,我們想要看到服務端分析和結果展示,首先需要將伺服器上生成的核心轉儲檔案轉儲到雲端,但是與之前的 CPU Profile 和堆快照的轉儲不一樣的地方在於,核心轉儲檔案的分析需要我們提供對應 Node.js 程序的啟動執行檔案,即 AliNode runtime 檔案,這裡簡化處理為只需要設定 Runtime 版本即可:

image.png

點選 設定 runtime 版本 即可進行設定,格式為 alinode-v{x}.{y}.{z} 的形式,比如 alinode-v3.11.5,版本會進行校驗,請務必填寫你的應用真實在使用的 AliNode runtime 版本。版本填寫完成後,我們就可以點選 轉儲 按鈕進行檔案轉儲到雲端的操作了:

image.png

顯然對於核心轉儲檔案來說,Chrome devtools 是沒有提供解析功能的,所以這裡只有一個 AliNode 定製的分析按鈕,點選這個 分析 按鈕,即可以看到結果:

image.png

這裡第一欄的概覽資訊看文字描述就能理解其含義,所以這裡就不再多做解釋了,我們來看下比較重要的預設檢視 BackTrace 資訊檢視,此檢視下展示的實際上是 Node.js 應用在 Crash 時刻的執行緒資訊,許多開發者認為 Node.js 是單執行緒的執行模型,其實這句話也不是完全錯誤,更準確的說法是 單主 JavaScript 工作執行緒,因為實際上 Node.js 還會開啟一些後臺執行緒來處理諸如 GC 裡的部分任務。

絕大部分的情況下,應用的 Crash 都是由 JavaScript 工作執行緒引發的,因此我們需要關注的也僅僅是這個執行緒,這裡顯然 BackTrace 資訊檢視中將 JavaScript 工作執行緒做了標紅和置頂處理,展開後可以看到 Node.js 應用 Crash 那一刻的錯誤堆疊資訊:

image.png

因為就算在 JavaScript 的工作執行緒中,也會存在 Native C/C++ 程式碼的穿透,但是在問題排查中我們往往只需要去看同樣標紅的 JavaScript 棧資訊即可,像在這個簡單的例子中,顯然 Crash 是因為檢視去啟動一個不存在的 JS 檔案導致的。

值得一提的是,核心轉儲檔案的分析功能非常的強大,因為在預備節中我們提到其生成的途徑除了 Node.js 應用 Crash 的時候由系統核心控制輸出外,還可以由 gcore 這樣的命令手動強制輸出,而本小節我們又看到核心轉儲檔案的分析實際上可以看到此刻的 JavaScript 棧資訊以及其入參,結合這兩點,我們可以在線上出現 CPU Profile 一節中提到的類死迴圈問題時直接採用 gcore 生成核心轉儲檔案,然後上傳至平臺雲端進行分析,這樣不僅可以看到我們的 Node.js 應用是阻塞在哪一行的 JavaScript 程式碼,甚至引發阻塞的引數我們也能完整獲取到,這對本地復現定位問題的幫助無疑是無比巨大的。

結尾

本節其實給大家介紹了 Node.js 效能平臺 的整套面向 Node.js 應用開發的監控、告警、分析和定位問題的解決方案的架構和最佳實踐,希望能讓大家對平臺的能力和如何更好地結合自身專案進行使用有一個整體的理解。

限於篇幅,最佳實踐中的 CPU Profile、堆快照和核心轉儲檔案的分析例子都非常的簡單,這部分的內容也更多的是旨在幫助大家理解平臺提供的工具如何使用以及其分析結果展示的指標含義,那麼本書的第三節中,我們會通過一些實際的生產遇到的案例問題藉助於 Node.js 效能平臺 提供的上述工具分析過程,來幫助大家更好的理解這部分資訊,也希望大家在讀完這些內容後能有所收穫,能對 Node.js 應用在生產中的使用更有信心。

作者:奕鈞

原文連結

​本文為雲棲社群原創內容,未經