.net程序內(nèi)存異常的原因及解決
一、概要
大概在今年三月份的時(shí)候突然被緊急調(diào)到另外一個(gè)項(xiàng)目組解決線(xiàn)上內(nèi)存異常問(wèn)題。經(jīng)過(guò)兩周的玩命奮戰(zhàn)終于解決了這個(gè)問(wèn)題這里把心路歷程及思路分享給大家。希望可以幫助到各位或現(xiàn)在正遇到這樣事情的小伙伴提供一些思路。
二、場(chǎng)景
當(dāng)部門(mén)老大找到我的時(shí)候,給我描述了這樣一段話(huà)。
“目前服務(wù)出現(xiàn)了提交內(nèi)存異常的問(wèn)題,目前分析出來(lái)可能是日志組件有大量的日志消息堆積把內(nèi)存占滿(mǎn)導(dǎo)致服務(wù)崩潰了。在國(guó)內(nèi)某地區(qū)客戶(hù)的服務(wù)器上15000臺(tái)物聯(lián)網(wǎng)設(shè)備不能正常工作這個(gè)問(wèn)題非常緊急需要馬上解決?!?/p>
問(wèn)題描述至此,沒(méi)有其他可用信息。這時(shí)候我先崩潰了...但是任務(wù)找到你不能說(shuō)不行。萬(wàn)一解決了這種重大事故還能在部門(mén)老大面前秀一把。
三、思路
(1)分析
part1,分析日志堆積原因
- 拿到服務(wù)器地址去翻出日志文件,查看日志內(nèi)容;內(nèi)容基本上都是一些報(bào)錯(cuò)情況xxx對(duì)象為null,對(duì)象轉(zhuǎn)換失敗。
- 日志組件的實(shí)現(xiàn)也比較糟糕log對(duì)象在每個(gè)調(diào)用的類(lèi)里都會(huì)重新new
解決方案:
- 修復(fù)對(duì)象為null的問(wèn)題并加上空值判斷,大概的原因就是json值轉(zhuǎn)換的時(shí)候傳入的值是null那么就引起這兩塊的連鎖反應(yīng)。非常值得注意的一點(diǎn)是通常json對(duì)象轉(zhuǎn)換的地方都會(huì)加入try塊去捕獲異常在程序里try的捕捉是會(huì)對(duì).net程序造成性能影響的所以能用判斷規(guī)避的盡量不要去觸發(fā)try機(jī)制,程序性能被拖下去其他方面的處理就會(huì)變相的削減處理速度變慢那么數(shù)據(jù)堆積好像就解釋的通了。
- 將日志組件重構(gòu)為單例且線(xiàn)程安全的實(shí)現(xiàn),寫(xiě)入日志的數(shù)據(jù)結(jié)構(gòu)體是class這里改成struct,考慮的因素是引用類(lèi)型會(huì)存在引用問(wèn)題再就是考慮的值類(lèi)型和引用類(lèi)型在內(nèi)存中占用的大小是不一樣的,而且值類(lèi)型和引用類(lèi)型在處理速度上值類(lèi)型更快。
以為這樣就結(jié)束了嗎?不,當(dāng)程序改好之后放在測(cè)試服務(wù)器上跑第二天早上測(cè)試部的小姐姐就找到我說(shuō)異常報(bào)錯(cuò)情況是好了,但是內(nèi)存泄漏還是沒(méi)解決。
part2,查找內(nèi)存泄漏的根本原因
看來(lái)part1的操作僅僅只是修復(fù)了一個(gè)小bug而已,并不是我所想的那么簡(jiǎn)單,在日志的查看中還發(fā)現(xiàn)log日志中出現(xiàn)“tcp服務(wù)拒絕連接xxx異?!薄.?dāng)我看到這些的時(shí)候心情糟糕透了....
1.一早我就用profile把服務(wù)程序跑了一遍發(fā)現(xiàn)了
(1)有幾個(gè)消息隊(duì)列占用非常大,查閱代碼之后發(fā)現(xiàn)服務(wù)端程序會(huì)和15000臺(tái)物聯(lián)網(wǎng)設(shè)備進(jìn)行交互的所有數(shù)據(jù)都會(huì)先堆積到這個(gè)隊(duì)列里如果這個(gè)隊(duì)列滿(mǎn)了(queue上限被設(shè)定2w)會(huì)new新的queue然后把溢出的部分轉(zhuǎn)到新的queue里,最可怕的是從隊(duì)列里取數(shù)據(jù)的還是單線(xiàn)程處理。
(2)還會(huì)有很多磁盤(pán)i/o的操作會(huì)存儲(chǔ)在應(yīng)用服務(wù)器本機(jī)上例如socket通訊的報(bào)文和需要轉(zhuǎn)發(fā)的內(nèi)容等等都會(huì)進(jìn)行寫(xiě)入操作。
(3)逐步調(diào)試的時(shí)候發(fā)現(xiàn)大部分的方法實(shí)現(xiàn)都是同步方法,而且框架版本居然是.net freamwork4。
解決方案:
(1)
【移除new新隊(duì)列的機(jī)制、刪除main queue的上限設(shè)置改為多線(xiàn)程處理queue;一切數(shù)據(jù)堆積的本質(zhì)就是數(shù)據(jù)處理不過(guò)來(lái)所以開(kāi)辟再多的內(nèi)存空間都是慢性死亡而已?!?br>
【走訪(fǎng)物聯(lián)網(wǎng)硬件部門(mén),詢(xún)問(wèn)物聯(lián)網(wǎng)設(shè)備發(fā)送數(shù)據(jù)頻率、設(shè)備數(shù)、單臺(tái)設(shè)備發(fā)送單條數(shù)據(jù)的大小是多少kb;為什么需要了解?這些第一點(diǎn)在程序內(nèi)記錄日志然后統(tǒng)計(jì)成走勢(shì)圖能直接觀(guān)察隊(duì)列內(nèi)部的變化開(kāi)會(huì)的時(shí)候能給領(lǐng)導(dǎo)具有說(shuō)服力的證據(jù)能看到數(shù)據(jù)量什么時(shí)候陡增、數(shù)據(jù)大小等;第二點(diǎn)因?yàn)檫@些報(bào)文數(shù)據(jù)需要存在應(yīng)用服務(wù)器本地那么這時(shí)候就能計(jì)算出寫(xiě)入的數(shù)據(jù)量有沒(méi)有超出普通硬盤(pán)的寫(xiě)入i/o瓶頸以及網(wǎng)絡(luò)帶寬的占用。】
【走訪(fǎng)物聯(lián)網(wǎng)硬件部門(mén)2,詢(xún)問(wèn)物聯(lián)網(wǎng)設(shè)備socket傳輸數(shù)據(jù)時(shí)是否有走正?!皌cp揮手”流程;為什么?因?yàn)閟ocket tcp通訊中,是雙工通道那么其中有一端突然斷開(kāi),另一端會(huì)進(jìn)入“wait”狀態(tài)不會(huì)及時(shí)回收tcp連接資源,大家試想一下如果15000臺(tái)設(shè)備高頻短連接去操作那么服務(wù)端連接隊(duì)列資源很有可能吃不消。這個(gè)時(shí)候就需要服務(wù)端主動(dòng)斷開(kāi)“失效”連接及時(shí)回收資源“拆除雙工通道”以及調(diào)整socket連接隊(duì)列大小?!?/p>
(2)磁盤(pán)寫(xiě)入報(bào)文信息這塊,就要用三寸不爛之舌說(shuō)動(dòng)項(xiàng)目經(jīng)理把這塊砍掉以節(jié)約cpu性能以及減少磁盤(pán)i/o,大伙試想一下每次socket通訊進(jìn)行收發(fā)的時(shí)候都要去操作一下i/o那是多么恐怖的一件事情;最后溝通結(jié)果那個(gè)組的項(xiàng)目經(jīng)理同意砍掉部分模塊磁盤(pán)寫(xiě)入功能,那么問(wèn)題來(lái)了剩下的怎么辦如何將優(yōu)勢(shì)進(jìn)一步擴(kuò)大?這時(shí)候繼續(xù)查閱項(xiàng)目代碼,結(jié)果發(fā)現(xiàn)socket通訊中“收”、“發(fā)”都會(huì)操作一次。那么這時(shí)候需要做的是將報(bào)文積累到一定數(shù)量比如說(shuō)積累1000條報(bào)文再一次性寫(xiě)入那么磁盤(pán)i/o的操作頻率將成倍遞減。
(3)最后一個(gè)問(wèn)題,就是講所有的方法修改為異步方法。這時(shí)候就能祭出task、async、await了。但是基于的框架是.net freamwork4的,后來(lái)又去查閱msdn的文檔發(fā)現(xiàn).net freamwork4遠(yuǎn)古框架中還是有這些特性的雖然用法稍微難受點(diǎn)但是還是能優(yōu)化的。一定要記住一點(diǎn),開(kāi)發(fā)服務(wù)端要有“服務(wù)端”思維如果都是同步方法就會(huì)被同步阻塞處于“等待處理結(jié)果狀態(tài)”這樣的話(huà)服務(wù)端的并發(fā)量是上不去的。
這里雖然沒(méi)怎么用上的一發(fā)大招,但是這里還是分享給大家“注釋大法”;注釋掉最有可能出問(wèn)題的地方逐一排查一定能發(fā)現(xiàn)問(wèn)題的所在就是非常的耗時(shí)那會(huì)我基本每天工作12小時(shí),尤其是公司的遠(yuǎn)古項(xiàng)目通常“代碼爛”、“設(shè)計(jì)基本沒(méi)有”、“使用的.net框架版本低”等等,一堆惡心人的事情發(fā)生。
(2)工具
- visual studio自帶的profile?!究梢苑治鯿pu、內(nèi)存等占用情況;這款比較推薦】
- vmmap【可以分析cpu、內(nèi)存等占用情況】
- ants performance profiler【這款工具比較強(qiáng)大能分析調(diào)用鏈路逐級(jí)告訴你內(nèi)存占用的地方以及內(nèi)存占用大小】
- window操作系統(tǒng)自帶的資源監(jiān)視器這個(gè)不用多說(shuō)大家都會(huì)用。
part3,總結(jié)
基于以上的修改,在測(cè)試服務(wù)器上穩(wěn)定運(yùn)行3周內(nèi)存穩(wěn)定在2.9g左右;
一定要記住:
- “遇到任何棘手的事情不要抱怨?!?/li>
- “一個(gè)優(yōu)秀的軟件工程招聘進(jìn)來(lái)就是解決問(wèn)題的,而不是制造問(wèn)題;”
- “對(duì)于任務(wù)的安排,高手永遠(yuǎn)都是說(shuō)出解決問(wèn)題的期限;到點(diǎn)交東西。而不是支支吾吾說(shuō)不清楚、退縮。”
- “遇到問(wèn)題冷靜思考,相信自己一定可以的;那怕失敗去嘗試一下也好?!?/li>
- “沒(méi)解決問(wèn)題的時(shí)候不要說(shuō)任何話(huà),說(shuō)什么都像是在找理由。閉上嘴巴去想辦法?!?/li>
其實(shí)解決這個(gè)問(wèn)題時(shí)期發(fā)生了很多有趣的故事,不過(guò)最終還是要解決難啃的問(wèn)題證明自己,開(kāi)發(fā)學(xué)習(xí)本身就是一個(gè)不斷變強(qiáng)的過(guò)程“修技術(shù),也修內(nèi)心”當(dāng)自己逐漸變強(qiáng)之后也不要鄙視技術(shù)不好的同事始終保持一顆學(xué)徒的心。
part4,彩蛋
解決這個(gè)問(wèn)題之后在同部門(mén)同事的眼里威望都會(huì)有提升(尤其是測(cè)試部門(mén)的小姐姐,因?yàn)樗齻儾挥觅M(fèi)力的每天去看服務(wù)器了),最終解決項(xiàng)目的重大事故部門(mén)老大給了機(jī)會(huì)調(diào)到其他省的研發(fā)中心當(dāng)項(xiàng)目經(jīng)理薪資平移的基礎(chǔ)上再上浮百分之十。可見(jiàn)掌握一手救急的技能有多么劃算。
以上就是.net程序內(nèi)存異常的原因及解決的詳細(xì)內(nèi)容,更多關(guān)于.net程序內(nèi)存異常的資料請(qǐng)關(guān)注碩編程其它相關(guān)文章!