Node.js 应用故障排查手册 —— Node.js 性能平台使用指南
楔子
前一節(jié)中我們借助于 Chrome devtools 實現(xiàn)了對線上 Node.js 應(yīng)用的 CPU/Memory 問題的排查定位,但是在實際生產(chǎn)實踐中,大家會發(fā)現(xiàn) Chrome devtools 更加偏向本地開發(fā)模式,因為顯然 Chrome devtools 不會負(fù)責(zé)去生成分析問題所需要的 Dump 文件,這意味著開發(fā)者還得額外在線上項目中設(shè)置好?v8-profiler?和?heapdump?這樣的工具,并且通過額外實現(xiàn)的服務(wù)來能夠去對線上運行的項目進(jìn)行實時的狀態(tài)導(dǎo)出。
加上實際上預(yù)備章中除了 CPU/Memory 的問題,我們還會遇到一些需要分析錯誤日志、磁盤和核心轉(zhuǎn)儲文件等才能定位問題的狀況,因此在這些場景下,僅僅靠 Chrome devtools 顯然會有一些力不從心。正是為了解決廣大 Node.js 開發(fā)者的這些痛點,我們在這里推薦大家在使用?Node.js 性能平臺,即原來的 AliNode,它已經(jīng)在阿里巴巴集團(tuán)內(nèi)部承載了幾乎所有的 Node.js 應(yīng)用線上運行監(jiān)控和問題排查,因此大家可以放心在生產(chǎn)環(huán)境部署使用。
本節(jié)將從?Node.js 性能平臺?的設(shè)計架構(gòu)、核心能力以及最佳實踐等角度,幫助開發(fā)者更好地使用這一工具來解決前面提到的異常指標(biāo)分析和線上 Node.js 應(yīng)用故障定位。
本書首發(fā)在 Github,倉庫地址:https://github.com/aliyun-node/Node.js-Troubleshooting-Guide,云棲社區(qū)會同步更新。
架構(gòu)
Node.js 性能平臺其實簡單的說由三部分組成:云控制臺?+?AliNode runtime?+?Agenthub,如下圖所示:
具體的部署步驟可以查看官方文檔:自助式部署 Runtime。借助于 Node.js 性能平臺的整套解決方案,我們可以很方便地實現(xiàn)預(yù)備章節(jié)中提到的絕大部分異常指標(biāo)的告警分析的能力。在生產(chǎn)實踐過程中,實際上在筆者看來,Node.js 性能平臺解決方案其實僅僅是提供了三個最核心卻也是最有效的能力:
- 異常指標(biāo)告警,即預(yù)備節(jié)中一些異常指標(biāo)出現(xiàn)異常時能通過短信/釘釘通知到開發(fā)者
- 導(dǎo)出線上 Node.js 應(yīng)用狀態(tài),包括但不限于即前面 Chrome devtools 一節(jié)中提到的 CPU/Memory 狀態(tài)導(dǎo)出
- 在線分析結(jié)果和更好的 UI 展示,定制化解析應(yīng)用導(dǎo)出狀態(tài)和展示,更符合國內(nèi)開發(fā)者習(xí)慣
換言之,Node.js 性能平臺作為一個產(chǎn)品本身功能也在不斷迭代新增修改中,但是以上的三個核心能力一定是第一優(yōu)先級保障的,其它邊邊角角的功能則相對來說響應(yīng)優(yōu)先級沒有那么高。
實際上我們也理解作為使用平臺的開發(fā)者希望能在一個地方看到 Node.js 線上應(yīng)用從底層到業(yè)務(wù)層的所有細(xì)節(jié),然而我個人感覺不同的工具都有應(yīng)該有其核心的能力輸出,很多時候不斷做加法容易讓產(chǎn)品本身定位模糊化以及泛而不精,Node.js 性能平臺實際上始終在致力于讓原本線上黑盒的運行時狀態(tài),能更加直觀地反饋給開發(fā)者,讓 Node.js 應(yīng)用的開發(fā)者面對一些偏向底層的線上疑難雜癥能夠不再無所適從。
最佳實踐
I. 配置合適的告警
線上應(yīng)用的告警實際上是一種自我發(fā)現(xiàn)問題的保護(hù)機(jī)制,如果沒有告警能力,那每次都會等到問題暴露到用戶側(cè)導(dǎo)致其反饋才能發(fā)現(xiàn)問題,這顯然對用戶體驗非常的不友好。
因此部署完成一個項目后,開發(fā)者首先需要去配置合適的告警,而在我們的生產(chǎn)實踐中,線上問題通過錯誤日志、Node.js 進(jìn)程 CPU/Memory 的分析、核心轉(zhuǎn)儲(Core dump)的分析以及磁盤分析能夠得出結(jié)論,因此我們需要的基本的告警策略也是源自以上五個部分。幸運的是平臺已經(jīng)給我們預(yù)設(shè)好了這些告警,大家只需要選擇一下即可完整這里的告警配置,如下圖所示:
在?Node.js 性能平臺?的告警頁面上有?快速添加規(guī)則,點開選中后會自動生成告警規(guī)則的閾值表達(dá)式模板和報警說明模板,我們可以按照項目實際監(jiān)控需求進(jìn)行修改,比如想要對 Node.js 進(jìn)程的堆內(nèi)存進(jìn)行監(jiān)控,可以選中?Memory 預(yù)警?選項,如下圖所示:
此時點擊?添加報警項?即完整了對進(jìn)程堆內(nèi)存的告警,并且將出現(xiàn)告警時需要點擊?通知設(shè)置->添加到聯(lián)系人列表?來添加的聯(lián)系人加入此條規(guī)則,如下圖所示:
那么在例子中的這條默認(rèn)的規(guī)則里,當(dāng)我們的 Node.js 進(jìn)程分配的堆內(nèi)存超過堆上線的 80%(默認(rèn) 64 位機(jī)器上堆上限是 1.4G)時會觸發(fā)短信通知到配置綁定到此條規(guī)則的聯(lián)系人。
實際上快速添加規(guī)則列表中給大家提供的是最常見的一些預(yù)配置好的告警策略,如果這些尚不能滿足你的需求,更多定制化的自定義的服務(wù)告警策略配置方法可以看官方文檔?報警設(shè)置。并且除了短信告警,也支持釘釘機(jī)器人推送告警消息到群,方便群發(fā)感知線上 Node.js 應(yīng)用態(tài)勢。
II. 按照告警類型進(jìn)行分析
按照 I 節(jié)中配置完成合適的告警規(guī)則后,那么當(dāng)收到告警短信時就可以按照策略類型進(jìn)行對應(yīng)的分析了。本節(jié)將按照預(yù)備節(jié)中比較常見的五大類問題來逐一講解。
a. 磁盤監(jiān)控
這個是比較好處理的問題,在快速添加的規(guī)則里實際上我們會在服務(wù)器的磁盤使用超過 85% 時進(jìn)行告警,那么收到磁盤告警后,可以連接到服務(wù)器,使用如下命令查看那個目錄占用比較高:
sudo du -h --max-depth=1 /找到占比比較高的目錄和文件后,看看是否需要備份后刪除來釋放出磁盤空間。
b. 錯誤日志
收到特定的錯誤日志告警后,只需要去對應(yīng)的項目的?Node.js 性能平臺?控制臺找到問題?實例?去查看其?異常日志?即可,如下圖所示:
這里會按照錯誤類型進(jìn)行規(guī)整,大家可以結(jié)合展示的錯誤棧信息來進(jìn)行對應(yīng)的問題定位。注意這里的錯誤日志文件需要你在部署 Agenthub 的時候?qū)懭肱渲梦募?#xff0c;詳細(xì)可以參見文檔?配置和啟動 Agenthub?一節(jié)中的?詳細(xì)配置?內(nèi)容。
c. 進(jìn)程 CPU 高
終于到了前一節(jié)中借助?v8-profiler?導(dǎo)出 CPU Profile 文件再使用 Chrome devtools 進(jìn)行分析的異常類型了。那么在?Node.js 性能平臺?的整套解決方案下,我們并不需要額外的去依賴類似 v8-profiler 這樣的第三方庫來實現(xiàn)進(jìn)程狀態(tài)的導(dǎo)出,與此相對的,當(dāng)我們收到 Node.js 應(yīng)用進(jìn)程的 CPU 超過我們設(shè)置的閾值告警時,我們只需要在控制臺對應(yīng)的?實例?點擊?CPU Profile?按鈕即可:
默認(rèn)會給抓取的進(jìn)程生成 3 分鐘的 CPU Profile 文件,等到結(jié)束后生成的文件會顯示在?文件?頁面:
此時點擊?轉(zhuǎn)儲?即可上傳到云端以供在線分析展示了,如下圖所示:
這里可以看到有兩個?分析?按鈕,其實第二個下標(biāo)帶有?(devtools)?的分析按鈕實際上就是前一節(jié)中提到的 Chrome devtools 分析,這里不再重復(fù)講解了,如果有遺忘的同學(xué)可以再去回顧下本大章前一節(jié)的內(nèi)容。我們重點看下第一個 AliNode 定制的分析,點擊第一個分析按鈕后,可以在新頁面看到如下所示內(nèi)容:
這里其實也是火焰圖,但與 Chrome devtools 提供的火焰圖不一樣的地方在于,這里是將抓取的 3 分鐘內(nèi)的 JS 函數(shù)執(zhí)行進(jìn)行了聚合展示出來的火焰圖,在一些存在多次執(zhí)行同一個函數(shù)(可能每次執(zhí)行非常短)的情況下,聚合后的火焰圖可以很方便的幫助我們找到代碼的執(zhí)行瓶頸來進(jìn)行對應(yīng)的優(yōu)化。
值得一提的是,如果你使用的 AliNode runtime 版本在?v3.11.4?或者?v4.2.1?以上(包含這兩個版本)的話,當(dāng)你的應(yīng)用出現(xiàn)類死循環(huán)問題,比如由于異常的用戶參數(shù)導(dǎo)致的正則回溯(即執(zhí)行完這個正則要十幾年,類似于 Node.js 進(jìn)程發(fā)生了死循環(huán))這類問題時,可以通過抓取 CPU Profile 文件來很方便地定位到問題代碼,詳細(xì)信息有興趣的同學(xué)可以看下?Node.js 性能平臺支持死循環(huán)和正則攻擊定位。
d. 內(nèi)存泄漏
與 CPU 高的問題一樣,當(dāng)我們收到 Node.js 應(yīng)用進(jìn)程的堆內(nèi)存占據(jù)堆上限的比率超過我們設(shè)置的閾值時,我們也不需要類似?heapdump?這樣的第三方模塊來導(dǎo)出堆快照進(jìn)行分析,我們還是在控制臺對應(yīng)的?實例?點擊?堆快照?按鈕即可生成對應(yīng) Node.js 進(jìn)程的堆快照:
生成的堆快照文件同樣會顯示在?文件?列表頁面,點擊?轉(zhuǎn)儲?將堆快照上傳至云端以供接下來的分析:
與上面一樣,下標(biāo)帶有?(devtools)?的分析按鈕還是前一節(jié)中提到的 Chrome devtools 分析,這里還是著重解析下 AliNode 定制的第一個分析按鈕,點擊后新頁面如下圖所示:
首先解釋下上面的總覽欄目的內(nèi)容信息:
- 文件大小:?堆快照文件本身的大小
- Shallow Szie 總大小:?回顧下上一節(jié)中的內(nèi)容,GC 根的 Retained Size 大小其實就是堆大小,也等于堆上分配的所有對象的 Shallow Size 總大小,因此這里其實就是已使用的堆空間
- 對象個數(shù):?當(dāng)前堆上分配的 Heap Object 總個數(shù)
- 對象邊個數(shù):?這個稍微抽象一些,假如 Object A.b 指向另一個 Object B,我們則認(rèn)為表示指向關(guān)系的 b 屬性就是一條邊
- GC Roots 個數(shù):?V8 引擎實現(xiàn)的堆分配,其實并不是我們之前為了幫助大家理解簡化的只有一個 GC 根的情況,在實際的運行模型下,堆空間內(nèi)存在許多的 GC 根,這里是真實的 GC 根的個數(shù)
這部分的信息旨在給大家一個概覽,部分信息需要深入解讀堆快照才能徹底理解,如果你實在無法理解其中的幾個概覽指標(biāo)信息,其實也無傷大雅,因為這并不影響我們定位問題代碼。
簡單了解了概覽信息的含義后,接著我們來看對于定位 Node.js 應(yīng)用代碼段非常重要的信息,第一個是默認(rèn)展示的?可疑點?信息,上圖中的內(nèi)容表示 @15249 這個對象占據(jù)了堆空間 97.41% 的內(nèi)存,那么它很可能就是一個泄漏對象,這里又存在兩種可能:
- 此對象本身應(yīng)該被釋放但是卻沒有釋放,造成堆空間占用如此大
- 此對象的某些屬性應(yīng)該被釋放但是卻沒有釋放,造成表象是此對象占據(jù)大量的堆空間
要判斷是哪一種情況,以及追蹤到對應(yīng)的代碼段,我們需要點擊圖中的?簇視圖?鏈接進(jìn)行進(jìn)一步觀察:
這里繼續(xù)解釋下什么是簇視圖,簇視圖實際上是支配樹的一個別名,也就是這個視圖下我們看到的正是前面一節(jié)中提到的從可疑泄漏對象出發(fā)的支配樹視圖,它的好處是,在這個視圖下,父節(jié)點的 Retained Size 可以直接由其子節(jié)點的 Retained Size 累加后再加上父節(jié)點自身的 Shallow Size 得到,換言之,在這個視圖下我們層層展開即可以看到可疑泄漏對象的內(nèi)存究竟被哪些子節(jié)點占用了。
并且結(jié)合前一節(jié)的支配樹描述,我們可以知道支配樹下的父子節(jié)點關(guān)系,并不一定是真正的堆上空間內(nèi)的對象父子關(guān)系,但是對于那些支配樹下父子關(guān)系在真正的堆空間內(nèi)也存在父子節(jié)點關(guān)系的簇節(jié)點,我們將真正的?邊?也用淺紫色標(biāo)識出來,這部分的?邊?信息對于我們映射到真正的代碼段非常有幫助。在這個簡單的例子中,我們可以很清晰的看到可疑泄漏對象 @15249 實際上是下屬的 test-alinode.js 中存在一個 array 變量,其中存儲了四個 45.78 兆的數(shù)組導(dǎo)致的問題,這樣就找到了問題代碼可以進(jìn)行后續(xù)優(yōu)化。
而在實際生產(chǎn)環(huán)境的堆快照分析下,很多情況下簇視圖下的父子關(guān)系在真實的堆空間中并不存在,那么就不會有這些紫色的邊信息展示,這時候我們想要知道可疑泄漏對象如何通過 JavaScript 生成的對象間引用關(guān)系引用到后面真正占據(jù)掉堆空間的對象(比如上圖中的 40 多兆的 Array 對象),我們可以點擊?可疑節(jié)點自身的地址鏈接?:
這樣就進(jìn)入到以此對象為起點的堆空間內(nèi)真正的對象引用關(guān)系視圖?Search 視圖:
這個視圖因為反映的是堆空間內(nèi)各個 Heap Object 之間真正的引用連接關(guān)系,因此父對象的 Retained Size 并不能直接由子節(jié)點的 Retained Size 累加獲取,如上圖紅框內(nèi)的內(nèi)容,顯然這里的三個子節(jié)點 Retained Size 累加已經(jīng)超過 100%,這也是 Search 視圖和簇視圖很大的不同點。借助于 Search 視圖,我們可以根據(jù)其內(nèi)反映出來的對象和邊之間的關(guān)系來定位可疑泄漏對象具體是在我們的 JavaScript 代碼中的哪一部分生成。
其實看到這邊,一些讀者應(yīng)該意識到了這里的 Search 視圖實際上對應(yīng)的就是前一節(jié)中提到的 Chrome devtools 的 Containment 視圖,只不過這里的起始點是我們選中的對象本身罷了。
最后就是需要提一下?Retainers 視圖,它和前一節(jié)中提到的 Chrome devtools 中解析堆快照展示結(jié)果里面的 Retainers 含義是一致的,它表示對象的父引用關(guān)系鏈,我們可以來看下:
這里 globa@1279 對象的 clearImmediate 屬性指向 timers.js()@15325,而?timers.js()@15325 的 context 屬性指向了可疑的泄漏對象 system / Context@15249。
在絕大部分的情況下,通過結(jié)合?Search 視圖?和?Retainers 視圖?我們可以定位到指定對象在 JavaScript 代碼中的生成位置,而?簇視圖?下我們又可以比較方便的知道堆空間被哪些對象占據(jù)掉了,那么綜合這兩部分的信息,我們就可以實現(xiàn)對線上內(nèi)存泄漏的問題進(jìn)行分析和代碼定位了。
e. 出現(xiàn)核心轉(zhuǎn)儲
最后就是收到服務(wù)器生成核心轉(zhuǎn)儲文件(Core dump 文件)的告警了,這表示我們的進(jìn)程已經(jīng)出現(xiàn)了預(yù)期之外的 Crash,如果你的 Agenthub 配置正常的話,在?文件?->?Coredump 文件?頁面會自動將生成的核心轉(zhuǎn)儲文件信息展示出來:
和之前的步驟類似,我們想要看到服務(wù)端分析和結(jié)果展示,首先需要將服務(wù)器上生成的核心轉(zhuǎn)儲文件轉(zhuǎn)儲到云端,但是與之前的 CPU Profile 和堆快照的轉(zhuǎn)儲不一樣的地方在于,核心轉(zhuǎn)儲文件的分析需要我們提供對應(yīng) Node.js 進(jìn)程的啟動執(zhí)行文件,即 AliNode runtime 文件,這里簡化處理為只需要設(shè)置 Runtime 版本即可:
點擊?設(shè)置 runtime 版本?即可進(jìn)行設(shè)置,格式為?alinode-v{x}.{y}.{z}?的形式,比如 alinode-v3.11.5,版本會進(jìn)行校驗,請務(wù)必填寫你的應(yīng)用真實在使用的 AliNode runtime 版本。版本填寫完成后,我們就可以點擊?轉(zhuǎn)儲?按鈕進(jìn)行文件轉(zhuǎn)儲到云端的操作了:
顯然對于核心轉(zhuǎn)儲文件來說,Chrome devtools 是沒有提供解析功能的,所以這里只有一個 AliNode 定制的分析按鈕,點擊這個?分析?按鈕,即可以看到結(jié)果:
這里第一欄的概覽信息看文字描述就能理解其含義,所以這里就不再多做解釋了,我們來看下比較重要的默認(rèn)視圖?BackTrace 信息視圖,此視圖下展示的實際上是 Node.js 應(yīng)用在 Crash 時刻的線程信息,許多開發(fā)者認(rèn)為 Node.js 是單線程的運行模型,其實這句話也不是完全錯誤,更準(zhǔn)確的說法是?單主 JavaScript 工作線程,因為實際上 Node.js 還會開啟一些后臺線程來處理諸如 GC 里的部分任務(wù)。
絕大部分的情況下,應(yīng)用的 Crash 都是由 JavaScript 工作線程引發(fā)的,因此我們需要關(guān)注的也僅僅是這個線程,這里顯然 BackTrace 信息視圖中將 JavaScript 工作線程做了標(biāo)紅和置頂處理,展開后可以看到 Node.js 應(yīng)用 Crash 那一刻的錯誤堆棧信息:
因為就算在 JavaScript 的工作線程中,也會存在 Native C/C++ 代碼的穿透,但是在問題排查中我們往往只需要去看同樣標(biāo)紅的 JavaScript 棧信息即可,像在這個簡單的例子中,顯然 Crash 是因為視圖去啟動一個不存在的 JS 文件導(dǎo)致的。
值得一提的是,核心轉(zhuǎn)儲文件的分析功能非常的強(qiáng)大,因為在預(yù)備節(jié)中我們提到其生成的途徑除了 Node.js 應(yīng)用 Crash 的時候由系統(tǒng)內(nèi)核控制輸出外,還可以由?gcore?這樣的命令手動強(qiáng)制輸出,而本小節(jié)我們又看到核心轉(zhuǎn)儲文件的分析實際上可以看到此刻的 JavaScript 棧信息以及其入?yún)?#xff0c;結(jié)合這兩點,我們可以在線上出現(xiàn) CPU Profile 一節(jié)中提到的類死循環(huán)問題時直接采用?gcore?生成核心轉(zhuǎn)儲文件,然后上傳至平臺云端進(jìn)行分析,這樣不僅可以看到我們的 Node.js 應(yīng)用是阻塞在哪一行的 JavaScript 代碼,甚至引發(fā)阻塞的參數(shù)我們也能完整獲取到,這對本地復(fù)現(xiàn)定位問題的幫助無疑是無比巨大的。
結(jié)尾
本節(jié)其實給大家介紹了?Node.js 性能平臺?的整套面向 Node.js 應(yīng)用開發(fā)的監(jiān)控、告警、分析和定位問題的解決方案的架構(gòu)和最佳實踐,希望能讓大家對平臺的能力和如何更好地結(jié)合自身項目進(jìn)行使用有一個整體的理解。
限于篇幅,最佳實踐中的 CPU Profile、堆快照和核心轉(zhuǎn)儲文件的分析例子都非常的簡單,這部分的內(nèi)容也更多的是旨在幫助大家理解平臺提供的工具如何使用以及其分析結(jié)果展示的指標(biāo)含義,那么本書的第三節(jié)中,我們會通過一些實際的生產(chǎn)遇到的案例問題借助于?Node.js 性能平臺?提供的上述工具分析過程,來幫助大家更好的理解這部分信息,也希望大家在讀完這些內(nèi)容后能有所收獲,能對 Node.js 應(yīng)用在生產(chǎn)中的使用更有信心。
原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的Node.js 应用故障排查手册 —— Node.js 性能平台使用指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spark in action on K
- 下一篇: 阿里CTO:阿里所有技术和产品输出都将必