NodeJS 自 2009 年顯露人間,到如今已經六個年頭了,因爲各類緣由,中間派生出了個兄弟,叫作 iojs,最近兄弟繼續合體,衍生出了 nodejs4.0 版本,這東西算是 nodejs new 1.0
版本,原班人馬都統一到一個戰線上。我沒有太關注 nodejs 背後的開發,但一直是它的忠實使用者,通讀了 v4.1.2 的 文檔,感受從開發者角度去看,也沒啥大的變化,因此這兩個兄弟分開這麼久,主要是在底層內建模塊上作改造,上層建築還沒有有大的變動,具體能夠看 這篇文章。javascript
若是你一直用着 nodejs,然而一直都在寫最基本的小 demo,不多深刻的去剖析 nodejs 的性能問題,甚至連如何 debug 代碼、如何發現性能問題都不知從哪裏下手,那麼趕忙往下讀吧!php
命令行中調試 nodejs 代碼,這是最基礎的調試技能,如同咱們在 Chrome 控制中調試 JS 代碼通常,然而卻用的不多,由於他太原始,顯得比較麻煩。html
回顧下,咱們平時的調試方式:java
console.log()
,打印調試結果asserts
模塊,對調試區域進行 debug這兩種調試方式,都須要咱們顯式將各類 debug 信息嵌入到咱們的業務邏輯代碼中,而熟悉了命令行調試以後,咱們能夠更好地開啓本身的調試之旅。NodeJS 給咱們提供了 Debugger 模塊,內建客戶端,經過 TCP 將命令行的輸入傳送到內建模塊以達到調試的目的。node
在啓動文件時,添加第二個參數 debug
:web
➜ $ node debug proxy2.js < Debugger listening on port 5858 debug> . ok break in proxy2.js:1 > 1 var http = require('http'); 2 var net = require('net'); 3 var url = require('url'); debug>
調試代碼的時候存在兩個狀態,一個是操做調試的位置,好比下一步,進入函數,跳出函數等,此時爲 debug 模式;另外一個是查看變量的值,好比進入循環中,想查看循環計數器 i 的值,此時爲 repl(read-eval-per-line) 狀態,在 debug 模式下輸入 repl
便可進入 repl 狀態:算法
debug> repl Press Ctrl + C to leave debug repl > http print something about http >
按下 Ctrl+C
能夠從 repl 狀態回到 debug 狀態下,咱們也不須要記憶 debug 狀態下有多少調試命令,執行 help 便可:數據庫
debug> help Commands: run (r), cont (c), next (n), step (s), out (o), backtrace (bt), setBreakpoint (sb), clearBreakpoint (cb), watch, unwatch, watchers, repl, restart, kill, list, scripts, breakOnException, breakpoints, version
相關的命令不算不少:npm
命令 | 解釋 |
---|---|
cont, c | 進入下一個斷點 |
next, n | 下一步 |
step, s | 進入函數 |
out, o | 跳出函數 |
setBreakpoint(), sb() | 在當前行設置斷點 |
setBreakpoint(line), sb(line) | 在 line 行設置斷點 |
上面幾個是經常使用的,更多命令能夠戳這裏。api
咱們平時開發都使用 IDE 工具,實際上不少 IDE 工具已經集成了 NodeJS 的調試工具,好比 Eclipse、webStorm 等等,他們的原理依然是利用 Nodejs 的 Debugger 內建模塊,在這個基礎上進行了封裝。
細心的同窗會發現,當咱們使用 debug 參數打開一個 node 文件時,會輸出這樣一行文案:
Debugger listening on port 5858
能夠訪問下 http://localhost:5858
,會看到:
它告訴咱們 nodejs 在打開文件的時候啓動了內建調試功能,而且監聽端口 5858 過來的調試命令。除了在命令行中直接調試以外,咱們還能夠經過另外兩種方式去調試這個代碼:
node debug <URI>
, 經過 URI 鏈接調試,如 node debug localhost:5858
node debug -p <pid>
經過 PID 連接調試若是咱們使用 --debug
參數打開文件:
➜ $ node --debug proxy2.js
此時,nodejs 不會進入到命令行模式,而是直接執行代碼,可是依然會開啓內建調試功能,這就意味着咱們具有了遠程調試 NodeJS 代碼的能力,使用 --debug
參數打開服務器的 nodejs 文件,而後經過:
➜ $ node debug <服務器IP>:<調試端口,默認5858>
能夠在本地遠程調試 nodejs 代碼。不過這裏須要區分下 --debug
和 --debug-brk
,前者會執行完全部的代碼,通常是在監聽事件的時候使用,然後者,不會執行代碼,須要等到外部調試接入後,進入代碼區。語言表述不會那麼生動,讀者能夠自行測試下。
默認端口號是 5858,若是這個端口被佔用,程序會遞增端口號,咱們也能夠指定端口:
➜ node node --debug-brk=8787 proxy2.js Debugger listening on port 8787
NodeJS 提供的內建調試十分強大,它告訴 V8,在執行代碼的時候中斷程度,等待開發者操控代碼的執行進度。咱們熟知的 node-inspector 也是用的這個原理。
➜ $ node-inspector --web-port 8080 --debug-port 5858
這裏的 --web-port
是 Chrome Devtools 的調試頁面地址端口,--debug-port
爲 NodeJS 啓動的內建 debug 端口,咱們能夠在 http://localhost:8080/debug?port=5858
打開頁面,調試使用 --debug(-brk)
參數打開的程序。
更多設置能夠查閱官方文檔。
Eclipse 和 webstorm 的工具欄中都有一個叫作 Run 的選擇欄,在這裏能夠配置該文件的執行方式,好比在 webstorm 中(Navigation>Run>Edit Configurations):
第一步,爲程序添加一個啓動程序
若是沒有 Nodejs 的選項(如在 phpstorm 中),能夠手動配置下。
第二步,配置執行項
Node interpreter
是你 node 程序的位置Node parameters
是開啓 nodejs 程序的選項,若是使用了 ES6 特性,須要開始 --harmony
模式,若是須要遠程調試程序,可使用 --debug
命令,咱們採用控制檯調試,顯然是不須要添加 --debug
參數的。Working directory
是文件的目錄Javascript file
是須要調試的文件第三步,斷點,調試
其餘 IDE 工具的調試大同小異,其原理也是經過 TCP 鏈接到 Nodejs 開啓的內建調試端口。
上面介紹了 NodeJS 調試須要掌握的幾個基本技能,掌握起來仍是很輕鬆的,可是要本身去嘗試下。
Nodejs 相比 Java、PHP 這些老牌語言,其周邊設施仍是有所欠缺的,如性能分析和監控工具等,加上它的單線程運行特性,在大型應用中,很容易讓系統的 CPU 或者內存達到瓶頸,從而致使程序崩潰。一旦發現程序警報 CPU 負載太高,或者內存飆高時,咱們該如何深刻排查 NodeJS 代碼存在的問題呢?
首先來分析下問題,內存飆高存在哪些方面的因素呢:
CPU 負載太高預警可能因素:
這些問題是最讓人頭疼的,一個項目幾十上百個文件,收到這些警報若是沒有經驗,根本無從下手排查。
最直接的手段就是分析 GC 日誌,由於程序的一舉一動都會反饋到 GC 上,而上述問題也會一一指向 GC,如:
這裏須要稍微插一段,NodeJS 的內存管理和垃圾回收機制。
V8 的內存分爲 New Space 和 Old Space,New Space 的大小默認爲 8M,Old Space 的大小默認爲 0.7G,64位系統這兩個數值翻倍。
對象的生命週期是:首先進入 New Space,在這裏,New Space 被平均分爲兩份,每次 GC 都會將一份中的活着的對象複製到另外一份,因此它的空間使用率是 50%,這個算法叫作 Cheney 算法,這個操做叫作 Scavenge。過一段時間,若是 New Space 中的對象還活着,會被挪到 Old Space 中去,GC 會每隔一段時間遍歷 Old Space 中死掉的對象,而後整理碎片(這裏有兩種模式 mark-sweep 和 mark-compact,不祥述)。上面提到的」死掉「,指的是對象已經沒有被引用了,活着說被引用的次數爲零了。
知道這些以後,咱們就好分析問題了,若是緩存增長(好比使用對象緩存了不少用戶信息),GC 是不知道這些緩存死了仍是活着的,他們會不停地查看這個對象,以及這個對象中的子對象是否還存活,若是這個對象數特別大,那麼 GC 遍歷的時間也會特別長。當咱們進行密集型計算的時候,會產生不少中間變量,這些變量每每在 New Space 中就死掉了,那麼 GC 也會在這裏屢次地進行 New Space 區域的垃圾回收。
說了這麼多,如何去分析 GC 的日誌?
在啓動程序的時候添加 --trace_gc
參數,V8 在進行垃圾回收的時候,會將垃圾回收的信息打印出來:
➜ $ node --trace_gc aa.js ... [94036] 68 ms: Scavenge 8.4 (42.5) -> 8.2 (43.5) MB, 2.4 ms [allocation failure]. [94036] 74 ms: Scavenge 8.9 (43.5) -> 8.9 (46.5) MB, 5.1 ms [allocation failure]. [94036] Increasing marking speed to 3 due to high promotion rate [94036] 85 ms: Scavenge 16.1 (46.5) -> 15.7 (47.5) MB, 3.8 ms (+ 5.0 ms in 106 steps since last GC) [allocation failure]. [94036] 95 ms: Scavenge 16.7 (47.5) -> 16.6 (54.5) MB, 7.2 ms (+ 1.3 ms in 14 steps since last GC) [allocation failure]. [94036] 111 ms: Mark-sweep 23.6 (54.5) -> 23.2 (54.5) MB, 6.2 ms (+ 15.3 ms in 222 steps since start of marking, biggest step 0.3 ms) [GC interrupt] [GC in old space requested]. ...
V8 提供了不少程序啓動選項:
啓動項 | 含義 |
---|---|
–max-stack-size | 設置棧大小 |
–v8-options | 打印 V8 相關命令 |
–trace-bailout | 查找不能被優化的函數,重寫 |
–trace-deopt | 查找不能優化的函數 |
這些啓動項均可以讓咱們查看 V8 在執行時的各類 log 日誌,對於排查隱晦問題比較有用。然而這堆日誌並不太好看,咱們能夠將日誌輸出來以後交給專業的工具幫咱們分析,相比不少人都用過 Chrome DevTools 的 JavaScript CPU Profile,它在這裏:
經過 Profile 能夠找到具體函數在整個程序中的執行時間和執行時間佔比,從而分析到具體的代碼問題,V8 也提供了 Profile 日誌導出:
➜ $ node --prof test.js
執行命令以後,會在該目錄下產生一個 *-v8.log
的日誌文件,咱們能夠安裝一個日誌分析工具 tick:
➜ $ sudo npm install tick -g ➜ $ node-tick-processor *-v8.log [Top down (heavy) profile]: Note: callees occupying less than 0.1% are not shown. inclusive self name ticks total ticks total 426 36.7% 0 0.0% Function: ~<anonymous> node.js:27:10 426 36.7% 0 0.0% LazyCompile: ~startup node.js:30:19 410 35.3% 0 0.0% LazyCompile: ~Module.runMain module.js:499:26 409 35.2% 0 0.0% LazyCompile: Module._load module.js:273:24 407 35.1% 0 0.0% LazyCompile: ~Module.load module.js:345:33 406 35.0% 0 0.0% LazyCompile: ~Module._extensions..js module.js:476:37 405 34.9% 0 0.0% LazyCompile: ~Module._compile module.js:378:37 ...
咱們也可使用 headdump 之類的工具將日誌導出,而後放到 Chrome 的 Profile 中去分析。
本文主要從 NodeJS 程序的調試手段上,以及調試性能的入口上作了簡要的介紹,但願對你有所啓發,不到之處還請斧正!