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