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