背景
近期孽鸡,為了評估服務(wù)性能搬男,測試同學(xué)對關(guān)鍵業(yè)務(wù)接口進行了壓測拣展,單臺NodeJS服務(wù)開啟3個進程的情況下,QPS最高達320多缔逛。為了確認服務(wù)是否還有優(yōu)化空間,我們使用阿里云的 NodeJS性能平臺 對服務(wù)進行分析,定位了服務(wù)的瓶頸褐奴,并在阿里云的同學(xué)幫助下采取了相應(yīng)的措施按脚,優(yōu)化了服務(wù)的性能。
問題排查與分析
Step1 獲取與分析CPU Profile
當我們以400并發(fā)量敦冬,對單一業(yè)務(wù)接口進行壓測辅搬,發(fā)現(xiàn)QPS為320時,服務(wù)器CPU被打滿脖旱。為了找到是什么原因?qū)е翪PU達到了性能瓶頸堪遂,我們使用了阿里云的「NodeJS性能平臺」,抓取了壓測時的 CPU Profile 信息萌庆。
經(jīng)過分析溶褪,我們發(fā)現(xiàn) _tickDomainCallback
和 garbage collector
在CPU占比很大,其中 _tickDomainCallback
占了50%多践险,GC
也占了27%的比例猿妈。通過展開 _tickDomainCallback
里的內(nèi)容,發(fā)現(xiàn)CPU占比高的邏輯主要是TypeORM 和4處業(yè)務(wù)邏輯巍虫。
Step2 排查數(shù)據(jù)庫性能
當我們看到TypeORM時彭则,我們以為是數(shù)據(jù)庫消費不過來(生產(chǎn)與消費能力不匹配,Query隊列產(chǎn)生大量堆積)占遥,導(dǎo)致TypeORM消耗大量CPU資源俯抖。后來,我們進行了第二次壓測瓦胎,并在服務(wù)器CPU打滿時獲取了RDS的性能分析報告芬萍。報告顯示:
- 數(shù)據(jù)庫CPU使用了15%的資源
- 平均查詢響應(yīng)速度小于15ms
- 無慢查詢記錄
- 無死鎖記錄
因此,我們排除了RDS導(dǎo)致TypeORM消耗CPU資源凛捏。我們推測可能與TypeORM本身的代碼有關(guān)担忧,我們使用了一個非常早期的TypeORM版本(v0.0.11)。阿里云的同學(xué)推薦我們升級TypeORM的版本試試坯癣,看看會不會有所改善瓶盛。但是最新的TypeORM版本與早期的版本API已經(jīng)發(fā)生了變化,無法進行平滑升級示罗。因此惩猫,放棄了對TypeORM優(yōu)化。
Step3 排查業(yè)務(wù)邏輯代碼
我們將可能影響性能的業(yè)務(wù)代碼進行了Review蚜点,發(fā)現(xiàn)優(yōu)化空間并不是很大轧房,代碼本身已經(jīng)經(jīng)過了精簡和優(yōu)化。無法進行進一步提升绍绘,我們將優(yōu)化重點放在了占比高達27%的 GC
上奶镶。
Step4 GC 信息抓取與分析
為了獲得詳細的GC信息迟赃,我們再次進行了壓測,并獲取了 GC Trace
信息厂镇。結(jié)果如下圖:
從圖中纤壁,我們可以獲取到一些重要信息:
- GC時間占比為26.87%
- 3分鐘內(nèi),GC暫時時間為47.8s捺信,且scavenge占了大多數(shù)
- 平均GC暫停時間為50~60ms
根據(jù)這些信息酌媒,我們可以得出 scavenge
非常頻繁,導(dǎo)致了CPU資源的占用迄靠。
scavenge
發(fā)生在新生代的內(nèi)存回收階段秒咨,這個階段觸發(fā)條件是, semi space allocation failed(半空間分配失敗)掌挚∮晗可以推測出,壓測期間我們的代碼邏輯頻繁的生成大量的小對象疫诽,導(dǎo)致 semi space
很快被分配滿舅世,從而導(dǎo)致了 scavenge 回收和CPU資源的占用。既然這樣奇徒,我們可不可通過調(diào)整 semi space
(半空間)的大小雏亚,減少GC的次數(shù)來優(yōu)化對CPU的占用。
Step5 GC 調(diào)優(yōu)與測試
NodeJS在64位系統(tǒng)上摩钙,默認的semi space大小為16M罢低。
我們將 semi space
進行了3次調(diào)整,分別設(shè)為64M胖笛、128M网持、256M,對不同值情況下的服務(wù)進行了壓測并獲取了對應(yīng) GC Trace
和 CPU Profile
长踊。
修改 semi space 方法
對于普通node服務(wù):
node index.js --max_semi_space_size=64
對于PM2啟動的服務(wù)功舀,在pm2的config文件中添加:
node_args: '--max_semi_space_size=64',
1) 64M
將 semi space
修改為64M,并進行線上壓測身弊,獲取壓測時的 GC Trace
和 CPU Profile
信息:
對比修改前的數(shù)據(jù)辟汰,我們發(fā)現(xiàn):
- GC的CPU占比從27.5%下降到了7.14%;
- 3分鐘內(nèi)GC次數(shù)阱佛,從1008次降到了312次帖汞。其中,Scavenge的次數(shù)從988次下降到了294次凑术;
- GC時間翩蘸,從原來的47.7s下降到了11.8s
- GC平均暫停時間在40ms左右
GC時間從47.7s下降到了11.8s,相應(yīng)的淮逊,QPS提升了10%催首。
2) 128M
將 semi space
調(diào)整到128M扶踊,得到的 GC Trace
和 CPU Profile
信息:
對比64M時的數(shù)據(jù),我們可以發(fā)現(xiàn):
- 與64M時GC次相比翅帜,GC次數(shù)從312下降到了145姻檀;
- Scavenge算法回收時間命满,增加了1倍涝滴。從平均50ms漲到了100ms;
- Mark-sweep的次數(shù)沒有發(fā)生變化
- CPU占比略微下降胶台,從7.14降到了6.71
可以看出歼疮,將 semi space
從64M調(diào)整到了128M,性能并沒有很大的提升诈唬。相反韩脏,Scavenge算法回收時間幾乎增長了一倍。
3) 256M
將 semi space
調(diào)整到256M铸磅,得到的 GC Trace
和 CPU Profile
信息:
可以觀察到:
- 與128M時相比赡矢,GC次數(shù)下降了一倍
- 但是Scavenge回收的時間,波動到了150ms阅仔。
- CPU占比吹散,也略微下降了一點,降到了5.99
可以看出八酒,將 semi space
調(diào)整到了 256M空民,性能并沒有顯著提升,且增加了 Scavenge
的回收時間羞迷。
小結(jié)
將 semi space
從16M調(diào)整到64M時界轩,GC的CPU占比從27.5%下降到了7.14%,Scavenge
算法平均回收耗時減少衔瓮,QPS提升了10%浊猾。繼續(xù)調(diào)大 semi space
,性能并沒有顯著提升热鞍,且Scavenge
算法回收時間增加葫慎。semi space
本身用于新生代對象快速分配,不適合調(diào)整過大碍现。因此幅疼,semi space
設(shè)置為64M較為合適。
總結(jié)
通過將semi space
調(diào)大昼接,觸發(fā) Scavenge
算法回收的概率降低爽篷,GC的次數(shù)也隨之減少。且 Scavenge
算法回收內(nèi)存的時間也較為合理慢睡,因而可以降低GC在CPU中的占比逐工。
本文主要介紹了線上服務(wù)的性能瓶頸的排查與GC調(diào)優(yōu)铡溪,并沒有介紹V8 垃圾回收機制的原理。推薦感興趣的同學(xué)泪喊,閱讀樸靈老師的《深入淺出Node.js》中關(guān)于《V8的垃圾回收機制》一節(jié)棕硫。其中,詳細了介紹了V8用到的各種算法袒啼,非常有助于理解性能調(diào)優(yōu)的原理哈扮。
最后,感謝一下阿里的奕鈞同學(xué)蚓再,在他的幫助下滑肉,幫我解決了問題。