前言
在服務器上調(diào)試node,是一件救火行為东帅,因為压固,如果是本地開發(fā),通過IDE提供的調(diào)試平臺我們可以進行一步一步的調(diào)試靠闭,在遠程服務器上調(diào)試帐我,會中斷服務進程,造成其他錯誤愧膀,推薦利用單元測試工具和做好合理的日志進程調(diào)試拦键,萬不得已,后院著火了檩淋,再使用服務器上的調(diào)試方法芬为。
Debugger
V8提供了標準的調(diào)試API,這個工具在node中是通過在語句中添加debugger的形式進行的蟀悦,同時媚朦,啟動的時候也需要帶上debug參數(shù)才可以實現(xiàn)。詳見下方
node debug xxx.js
x = 5;
setTimeout(function () {
debugger;
console.log("world");
}, 1000);
console.log("hello");
$ node debug examples/B/myscript.js
< debugger listening on port 5858
connecting... ok
break in examples/B/myscript.js:2
1 // myscript.js
2 x = 5;
3 setTimeout(function () {
4 debugger;
debug>
代碼中遇到debugger日戈;后會中斷询张,可以通過以下的命令進行debug。
命令 | 說明 |
---|---|
cont | 或者c浙炼,繼續(xù)執(zhí)行 |
next | 或者n份氧,執(zhí)行到下一個斷點 |
step | 或者s唯袄,步進到函數(shù)內(nèi)部 |
out | 或者o,從函數(shù)內(nèi)部跳出 |
pause | 暫停執(zhí)行 |
setBreakpoint() | 或者sb()蜗帜,在當前行設置斷點 |
setBreakpoint(lineNo) | 或者sb(lineNo)恋拷,在指定行設置斷點 |
setBreakpoint('fn()') | 或者sb('fn()'),在函數(shù)體的第一個聲明處設置斷點 |
setBreakpoint('xxx.js') | 或者sb('xxx.js')厅缺,在腳本文件的第一行設置斷點 |
clearBreakpoint | 或者cb()蔬顾,清除斷點 |
backtrace | 或者bt,打印當前執(zhí)行情況下的堆棧信息 |
list(5) | 列出當前上下文前后5行的源代碼 |
watch(expr) | 添加表達式到觀察者列表店归,進行觀察 |
unwatch(expr) | 從觀察列表中移除對表達式的觀察 |
watchers | 列出所有觀察的表達式和值 |
repl | 打開調(diào)試的交互阎抒,用于執(zhí)行調(diào)試腳本的上下文 |
對于已經(jīng)啟動的程序如何debug
通過向程序發(fā)送SIGUSR1信號開啟調(diào)試,kill -s USR1 10093
消痛,可以在瀏覽器上開啟調(diào)試且叁,使用工具Node Inspector。
安裝:npm install -g node-inspector
啟動的時候秩伞,直接node-inspector即可.....
當然逞带,使用node Inspector必須先啟用node進程的調(diào)試模式,也就是使用debug或者發(fā)送SIGUSR1給node進程來啟用調(diào)試模式纱新,啟動進程后展氓,調(diào)用Inspector工具,工具就與node進程建立了調(diào)試代理:
$ node-inspector
Node Inspector v0.5.0
info - socket.io started
Visit http://127.0.0.1:8080/debug?port=5858 to start debugging
剩下的就跟在IDE中調(diào)試代碼一樣了脸爱。
node調(diào)試的原理
在打開調(diào)試的時候遇汞,我們會看到這樣的輸出:
Debugger listening on port 5858
可以訪問下 http://localhost:5858
看到如下信息:
Type:connect
V8-Version:4.2.15.19
Protocol-Version:1
Embedding-Host:node v4.2.15
Content-Length:0
這個其實就是node調(diào)用呢內(nèi)建調(diào)試功能,并監(jiān)聽端口5858來使用調(diào)試命令簿废。我們還可以使用
node debug <URI>
node debug -p <pid>
來調(diào)用
如果我們使用 --debug 參數(shù)打開文件:
.
node --debug xxxx.js
此時空入,nodejs 不會進入到命令行模式,而是直接執(zhí)行代碼族檬,但是依然會開啟內(nèi)建調(diào)試功能歪赢,這就意味著我們具備了遠程調(diào)試 NodeJS 代碼的能力,使用 --debug 參數(shù)打開服務器的 nodejs 文件单料,然后通過:
node debug <服務器IP>:<調(diào)試端口埋凯,默認5858>
可以在本地遠程調(diào)試 nodejs 代碼。不過這里需要區(qū)分下 --debug 和 --debug-brk扫尖,前者會執(zhí)行完所有的代碼白对,一般是在監(jiān)聽事件的時候使用,而后者换怖,不會執(zhí)行代碼甩恼,需要等到外部調(diào)試接入后,進入代碼區(qū)。語言表述不會那么生動媳拴,讀者可以自行測試下。
默認端口號是 5858兆览,如果這個端口被占用屈溉,程序會遞增端口號,我們也可以指定端口:
node node --debug-brk=8787 xxxx.js
Debugger listening on port 8787
不過在最新的node中抬探,調(diào)試的原理以及有所改變:
可以看到直接推薦使用Inspector了子巾,因此,大家也就不必太過于在乎調(diào)試了小压,還是那句話:服務器上的debug比較麻煩线梗,還是通過良好的單元測試和服務器日志進行錯誤排查。
性能調(diào)試
我們可以使用pm2這樣的檢測工具來檢測node怠益,現(xiàn)在我們想先分享一下產(chǎn)生問題的原因仪搔。或者說是什么問題造成了cpu或內(nèi)存達到瓶頸蜻牢,從而導致系統(tǒng)奔潰的呢烤咧?在菜譜負載過高,內(nèi)存突然飆升的時候又如何發(fā)現(xiàn)這些問題呢抢呆?
分析原因
cpu負載過重的原因
1.垃圾回收頻率過高煮嫌、量過大,也給也是因為內(nèi)存飆升造成的后遺癥
2.計算密集型的長循環(huán)抱虐,比如遍歷大量的文件昌阿、文件夾、大量的計算等
內(nèi)存飆升的原因
1.緩存恳邀,很多人把內(nèi)存當緩存用懦冰,比如session存在內(nèi)存中
2.閉包,作用域沒有被及時釋放轩娶,造成常駐內(nèi)存占用過多
3.生成者的生產(chǎn)能力和消費者的消費能力不匹配儿奶,例如數(shù)據(jù)庫已經(jīng)忙不過來了,但是Query隊列中還有大量請求堆積鳄抒。
解決手段
一般情況下闯捎,我們可以通過分析GC日志來查找問題:
問題 | 解釋 |
---|---|
內(nèi)存飆升 | 特別是old space內(nèi)存的飆升,會直接導致GC的次數(shù)和時間增長 |
緩存增加 | 導致GC的時間增加许溅,無用遍歷過多(如果緩存增加(比如使用對象緩存了很多用戶信息)瓤鼻,GC 是不知道這些緩存死了還是活著的,他們會不停地查看這個對象贤重,以及這個對象中的子對象是否還存活茬祷,如果這個對象數(shù)特別大,那么 GC 遍歷的時間也會特別長并蝗。當我們進行密集型計算的時候祭犯,會產(chǎn)生很多中間變量秸妥,這些變量往往在 New Space 中就死掉了,那么 GC 也會在這里多次地進行 New Space 區(qū)域的垃圾回收沃粗。 |
密集型計算 | 導致GC new space次數(shù)增加粥惧。(也就是前邊說過的scavenage操作的cheney算法) |
分析GC日志
在啟動程序的時候添加 --trace_gc 參數(shù),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 | 查找不能被優(yōu)化的函數(shù)突雪,重寫 |
–trace-deopt | 查找不能優(yōu)化的函數(shù) |
這些啟動項都可以讓我們查看 V8 在執(zhí)行時的各種 log 日志,對于排查隱晦問題比較有用
另外涡贱,通過 Profile 可以找到具體函數(shù)在整個程序中的執(zhí)行時間和執(zhí)行時間占比咏删,從而分析到具體的代碼問題,V8 也提供了 Profile 日志導出:
$ node --prof test.js
//執(zhí)行命令之后问词,會在該目錄下產(chǎn)生一個 *-v8.log 的日志文件督函,我們可以安裝一個日志分析工具 tick:
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
...
解決問題
對于內(nèi)存當做緩存的,我們可以引入外部緩存如redis等戏售,對于閉包侨核,我們可以人為控制閉包的釋放,對于大文件灌灾,我們可以將其分解為小文件迭代的去讀取搓译,總之,方法多多锋喜,大家可以繼續(xù)探討