作為Javascript在伺服器端的一個執行時(Runtime),極大的豐富了Javascript的應用場景。
但是Node.js Runtime本身是一個黑盒,我們無法感知執行時的狀態,對於線上問題也難以復現。
因此效能監控是Node.js應用程式「正常執行」的基石。不僅可以隨時監控執行時的各項指標,還可以幫助排查異常場景問題。
效能監控可以分為兩個部分:
效能指標的採集和展示
效能資料的抓取和分析
從上圖可以看到目前主流的三種Node.js效能監控方案的優缺點,以下是簡單介紹這三種方案的組成:
Prometheus
AliNode
通過process.cpuUsage()
可以獲取當前程序的CPU耗時資料,返回值的單位是微秒
通過process.memoryUsage()
可以獲取當前程序的記憶體分配資料,返回值的單位是位元組
從上圖可以看出,rss
包含程式碼段(Code Segment
)、棧記憶體(Stack
)、堆記憶體(Heap
)
通過v8.getHeapStatistics()
和v8.getHeapSpaceStatistics()
可以獲取v8堆記憶體和堆空間的分析資料,下圖展示了v8的堆記憶體組成分佈:
堆記憶體空間先劃分為空間(space),空間又劃分為頁(page),記憶體按照1MB對齊進行分頁。
New Space:新生代空間,用來存放一些生命週期比較短的物件資料,平分為兩個空間(空間型別為semi space
):from space
,to space
Old Space:老生代空間,用來存放New Space
晉升的物件
Code Space:存放v8 JIT編譯後的可執行程式碼
Map Space:存放Object指向的隱藏類的指標物件,隱藏類指標是v8根據執行時記錄下的物件佈局結構,用於快速存取物件成員
Large Object Space:用於存放大於1MB而無法分配到頁的物件
v8的垃圾回收演演算法分為兩類:
Mark-Sweep-Compact
演演算法,用於老生代的物件回收Scavenge
演演算法,用於新生代的物件回收前提:New space
分為from
和to
兩個物件空間
觸發時機:當New space
空間滿了
步驟:
在from space
中,進行寬度優先遍歷
發現存活(可達)物件
Old space
to space
中當複製結束時,to space
中只有存活的物件,from space
就被清空了
交換from space
和to space
,開始下一輪Scavenge
適用於回收頻繁,記憶體不大的物件,典型的空間換時間的策略,缺點是浪費了多一倍的空間
三個步驟:標記、清除、整理
觸發時機:當Old space
空間滿了
步驟:
Marking(三色標記法)
marking queue
(顯式棧)中,並將這些物件標記為灰色marking queue
pop
出來,並標記為黑色push
到marking queue
上,如此往復Sweep
Compact
Old space
的一端,這樣清除出來的空間就是連續完整的在最開始v8進行垃圾回收時,需要停止程式的執行,掃描完整個堆,回收完記憶體,才會重新執行程式。這種行為就叫全停頓(Stop-The-World
)
雖然新生代活動物件較小,回收頻繁,全停頓,影響不大,但是老生代存活物件多且大,標記、清理、整理等造成的停頓就會比較嚴重。
這個理念其實有點像React框架中的Fiber架構,只有在瀏覽器的空閒時間才會去遍歷Fiber Tree執行對應的任務,否則延遲執行,儘可能少地影響主執行緒的任務,避免應用卡頓,提升應用效能。
由於v8對於新老生代的空間預設限制了大小
New space
預設限制:64位元系統為32M,32位元系統為16MOld space
預設限制:64位元系統為1400M,32位元系統為700M因此node
提供了兩個引數用於調整新老生代的空間上限
--max-semi-space-size
:設定New Space
空間的最大值--max-old-space-size
:設定Old Space
空間的最大值node
也提供了三種檢視GC紀錄檔的方式:
--trace_gc
:一行紀錄檔簡要描述每次GC時的時間、型別、堆大小變化和產生原因--trace_gc_verbose
:展示每次GC後每個V8堆空間的詳細狀況--trace_gc_nvp
:每次GC的詳細鍵值對資訊,包含GC型別,暫停時間,記憶體變化等由於GC紀錄檔比較原始,還需要二次處理,可以使用AliNode團隊開發的v8-gc-log-parser
對於執行程式的堆記憶體進行快照取樣,可以用來分析記憶體的消耗以及變化
生成.heapsnapshot
檔案有以下幾種方式:
使用heapdump
使用v8的heap-profile
使用nodejs內建的v8模組提供的api
v8.getHeapSnapshot()
v8.writeHeapSnapshot(fileName)
生成的.heapsnapshot
檔案,可以在Chrome devtools工具列的Memory,選擇上傳後,展示結果如下圖:
預設的檢視是Summary
檢視,在這裡我們要關注最右邊兩欄:Shallow Size
和 Retained Size
Shallow Size
:表示該物件本身在v8堆記憶體分配的大小Retained Size
:表示該物件所有參照物件的Shallow Size
之和當發現Retained Size
特別大時,該物件內部可能存在記憶體漏失,可以進一步展開去定位問題
還有Comparison
檢視是用於比較分析兩個不同時段的堆快照,通過Delta
列可以篩選出記憶體變化最大的物件
對於執行程式的CPU進行快照取樣,可以用來分析CPU的耗時及佔比
生成.cpuprofile
檔案有以下幾種方式:
這是採集5分鐘的CPU Profile樣例
生成的.cpuprofile
檔案,可以在Chrome devtools工具列的Javascript Profiler
(不在預設tab,需要在工具列右側的更多中開啟顯示),選擇上傳檔案後,展示結果如下圖:
預設的檢視是Heavy
檢視,在這裡我們看到有兩欄:Self Time
和Total Time
Self Time
:代表此函數本身(不包含其他呼叫)的執行耗時Total Time
:代表此函數(包含其他呼叫函數)的總執行耗時當發現Total Time
和Self Time
偏差較大時,該函數可能存在耗時比較多的CPU密集型計算,也可以展開進一步定位排查
當應用意外崩潰終止時,系統會自動記錄下程序crash掉那一刻的記憶體分配資訊,Program Counter以及堆疊指標等關鍵資訊來生成core檔案
生成.core
檔案的三種方法:
ulimit -c unlimited
開啟核心限制node --abort-on-uncaught-exception
node啟動新增此引數,可以在應用出現未捕獲的異常時也能生成一份core檔案gcore <pid>
手動生成core檔案獲取.core
檔案後,可以通過mdb、gdb、lldb等工具實現解析診斷實際程序crash的原因
llnode `which node` -c /path/to/core/dump
從監控可以觀察到堆記憶體在持續上升,因此需要堆快照進行排查
根據heapsnapshot
可以分析排查到有一個newThing
的物件一直保持著比較大的記憶體
從程式碼中可以看到雖然unused
方法沒有呼叫,但是newThing
物件是參照自theThing
,導致其一直存在於replaceThing
這個函數的執行上下文中,沒有被釋放,這就是典型的由於閉包產生的記憶體漏失案例
常見的記憶體漏失有以下幾種情況:
因此在上述這幾種情況時,一定要謹慎考慮物件在記憶體中是否會被自動回收,不會被自動回收的話,需要手動進行回收,比如手動把物件設定為null
、移除定時器、解綁事件監聽等
至此,本文已經對整個Node.js的效能監控體系進行了詳細的介紹。
首先,介紹了效能監控解決的問題,組成部分以及主流方案的優缺點對比。
然後,針對兩大部分效能指標和快照工具進行了具體的介紹,
最後,從觀察、分析、排查再現一個簡單的記憶體漏失案例,並總結了常見記憶體漏失的情況和解決方案。
希望這一篇文章能夠幫助大家對整個Node.js的效能監控體系有所瞭解。
更多node相關知識,請存取:!
以上就是為什麼需要效能監控?聊聊Node.js效能監控的詳細內容,更多請關注TW511.COM其它相關文章!