邵晨龍++江雪
[摘 要]Node對內(nèi)存泄漏十分敏感,一旦線上應(yīng)用有成千上萬的流量,哪怕是一個字節(jié)的內(nèi)存泄漏也會造成堆積,垃圾回收過程中將耗費更多時間進行對象掃描,應(yīng)用響應(yīng)緩慢,直到進程內(nèi)存溢出,應(yīng)用崩潰。本文不僅分析了造成內(nèi)存泄漏的原因,并介紹了幾種主流的排查方案。
[關(guān)鍵詞]Node 內(nèi)存泄漏
中圖分類號:TG294 文獻標識碼:A 文章編號:1009-914X(2017)46-0046-01
1 引言
內(nèi)存泄漏通常產(chǎn)生于無意間,較難排查。盡管內(nèi)存泄漏的情況不盡相同,但其實質(zhì)只有一個,那就是應(yīng)當回收的對象出現(xiàn)意外而沒有被回收,變成了常駐在老生代中的對象。
2 引發(fā)內(nèi)存泄漏的原因
通常,造成內(nèi)存泄漏的原因主要有兩個:緩存,隊列消費不及時。
2.1 緩存
緩存在應(yīng)用中的作用舉足輕重,可以十分有效地節(jié)省資源。因為它的訪問效率要比I/O的效率高,一旦命中緩存,就可以節(jié)省一次I/O的時間。
但是在Node中,緩存并非物美價廉。一旦一個對象被當作緩存來使用,那就意味著它將會常駐在老生代中。緩存中存儲的鍵越多,長期存活的對象也就越多,這將導致垃圾回收在進行掃描和整理時,對這些對象做無用功。
另一個問題在于,JavaScript開發(fā)者通常喜歡用對象的鍵值對來緩存東西,但這與嚴格意義上的緩存又有著區(qū)別,嚴格意義的緩存有著完善的過期策略,而普通對象的鍵值對并沒有。所以,在Node中,任何試圖拿內(nèi)存當緩存的行為都應(yīng)當被限制。當然,這種限制并不是不允許使用的意思,而是要小心為之。
為了解決緩存中的對象永遠無法釋放的問題,需要加入一種策略來限制緩存的無限增長,例如將記錄鍵記錄在數(shù)組中,一旦超過數(shù)量,就以先進先出的方式進行淘汰,這種策略適合于小場景的案例中使用,在比較大型的應(yīng)用場景一般使用的是基于LRU(最近最少使用)算法的策略。
除了限制緩存的大小外,還需要考慮到進程間是無法共享內(nèi)存的。如果在進程內(nèi)使用緩存,這些緩存不可避免地有重復(fù),對物理內(nèi)存的使用是一種浪費。目前較好的解決方案是采用進程外的緩存,進程自身不存儲狀態(tài)。外部的緩存軟件有著良好的緩存過期淘汰策略以及自身的內(nèi)存管理,不影響Node進程的性能。市面是較好的緩存有Redis和Memcached。
2.2 隊列
在解決了緩存帶來的內(nèi)存泄漏問題后,另一個不經(jīng)意產(chǎn)生的內(nèi)存泄漏則是隊列。在JavaScript中可以通過隊列(數(shù)組對象)來完成許多特殊的需求,比如Bagpipe。隊列在消費者-生產(chǎn)者模型中經(jīng)常充當中間產(chǎn)物。這是一個容易忽略的情況,因為在大多數(shù)應(yīng)用場景下,消費的速度遠遠大于生產(chǎn)的速度,內(nèi)存泄漏不易產(chǎn)生。但是一旦消費速度低于生產(chǎn)速度,將會形成堆積。
舉個例子,有的應(yīng)用會收集日志。如果欠缺考慮,也許會采用數(shù)據(jù)庫來記錄日志。日志通常會是海量的,數(shù)據(jù)庫構(gòu)建在文件系統(tǒng)之上,寫入效率遠遠低于文件直接寫入,于是會形成數(shù)據(jù)庫寫入操作的堆積,而JavaScript中相關(guān)的作用域也不會得到釋放,內(nèi)存占用不會回落,從而出現(xiàn)內(nèi)存泄漏。
遇到這種場景,表層的解決方案是換用消費速度更高的技術(shù)。在日志收集的案例中,換用文件寫入日志的方式會更高效。但是,如果生產(chǎn)速度因為某些原因突然激增,或者消費速度因為突然的系統(tǒng)故障而降低,內(nèi)存泄漏還是可能會出現(xiàn)。
深度的解決方案應(yīng)該是監(jiān)控隊列的長度,一旦堆積,應(yīng)當通過監(jiān)控系統(tǒng)產(chǎn)生報警并通知相關(guān)人員。另一個解決方案是任意異步調(diào)用都應(yīng)該包含超時機制,一旦在限定的時間內(nèi)未完成響應(yīng),通過回調(diào)函數(shù)傳遞超時異常,使得任意異步調(diào)用的回調(diào)都具備可控的響應(yīng)時間,給消費速度一個下限值。
對于Bagpipe而言,它提供了超時模式和拒絕模式。啟用超時模式時,調(diào)用加入到隊列中就開始計時,超時就直接響應(yīng)一個超時錯誤。啟用拒絕模式,當隊列擁塞時,新到來的調(diào)用會直接響應(yīng)擁塞錯誤。這兩種模式都能夠有效地防止隊列擁塞導致的內(nèi)存泄漏問題。
3 內(nèi)存泄漏的排查方案
在Node中,由于V8的堆內(nèi)存大小的限制,它對內(nèi)存泄漏非常敏感。當在線服務(wù)的請求量變大時,哪怕是一個字節(jié)的泄露都會導致內(nèi)存占用過高。
常見的用于排查Node應(yīng)用內(nèi)存泄露的有以下幾個工具:
*v8-profiler,用于對V8堆內(nèi)存抓取快照和對CPU進行分析;
*node-heapdump,允許對V8堆內(nèi)存抓取快照,用于事后分析,這是Node核心貢獻者編寫的模塊;
*node-mtrace,它使用了GCC的mtrace工具來分析堆的使用;
*node-memwatch,來自Mozilla的LloydHilaiel貢獻的模塊,采用WTFP許可發(fā)布。
4 總結(jié)
所謂知己知彼,百戰(zhàn)不殆,只有深入了解了造成內(nèi)存泄漏的主要原因,才能對癥下藥,盡可能地規(guī)避造成內(nèi)存泄漏的行為,并采用主流的工具予以排查消除隱患。endprint