记一次 .NET 某云采购平台API 挂死分析
一:背景
1. 講故事
大概有兩個(gè)月沒寫博客了,關(guān)注我的朋友應(yīng)該知道我最近都把精力花在了星球,這兩個(gè)月時(shí)間也陸陸續(xù)續(xù)的有朋友求助如何分析dump,有些朋友太客氣了,給了大大的紅包,哈哈????,手里面也攢了10多個(gè)不同問題類型的dump,后續(xù)也會(huì)逐一將分析思路貢獻(xiàn)出來。
這個(gè)dump是一位朋友大概一個(gè)月前提供給我的,由于wx里面求助的朋友比較多,一時(shí)也沒找到相關(guān)截圖,不得已破壞一下老規(guī)矩。????????????
既然朋友說api接口無響應(yīng),呈現(xiàn)了hangon現(xiàn)象,從一些過往經(jīng)驗(yàn)看,大概也只有三種情況。
大量鎖等待
線程不夠用
死鎖
有了這種先入為主的思想,那就上windbg說事唄。
二:windbg 分析
1. 有大量鎖等待嗎?
要想看是否鎖等待,老規(guī)矩,看一下 同步塊表。
0:000>?!syncblk Index?SyncBlock?MonitorHeld?Recursion?Owning?Thread?Info??SyncBlock?Owner ----------------------------- Total???????????1673 CCW?????????????3 RCW?????????????4 ComClassFactory?0 Free????????????397撲了個(gè)空,啥也沒有,那就暴力看看所有的線程棧吧。
不看還好,一看嚇一跳,有339個(gè)線程卡在了 System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object) 處,不過轉(zhuǎn)念一想,就算有339個(gè)線程卡在這里,真的會(huì)導(dǎo)致程序hangon嗎?也不一定,畢竟我看過有1000+的線程也不會(huì)卡死,只不過cpu爆高而已,接下來繼續(xù)研判一下是不是線程不夠用導(dǎo)致,可以從 線程池任務(wù)隊(duì)列 上面入手。
2. 探究線程池隊(duì)列
可以用 !tp 命令查看。
0:000>?!tp CPU?utilization:?10% Worker?Thread:?Total:?328?Running:?328?Idle:?0?MaxLimit:?32767?MinLimit:?4 Work?Request?in?Queue:?74Unknown?Function:?00007ffe91cc17d0??Context:?000001938b5d8d98Unknown?Function:?00007ffe91cc17d0??Context:?000001938b540238Unknown?Function:?00007ffe91cc17d0??Context:?000001938b5eec08...Unknown?Function:?00007ffe91cc17d0??Context:?0000019390552948Unknown?Function:?00007ffe91cc17d0??Context:?0000019390562398Unknown?Function:?00007ffe91cc17d0??Context:?0000019390555b30 -------------------------------------- Number?of?Timers:?0 -------------------------------------- Completion?Port?Thread:Total:?5?Free:?4?MaxFree:?8?CurrentLimit:?4?MaxLimit:?1000?MinLimit:?4從輸出信息看,線程池中328個(gè)線程全部打滿,工作隊(duì)列中還有74位客人在等待,綜合這兩點(diǎn)信息就已經(jīng)很清楚了,本次hangon是由于大量的客人到來超出了線程池的接待能力所致。
3. 接待能力真的不行嗎?
這個(gè)標(biāo)題我覺得很好,真的不行嗎?到底行不行,可以從兩點(diǎn)入手:
是不是代碼寫的爛?
qps是不是真的超出了接待能力?
要想找出答案,還得從那 339 個(gè)卡死的線程說起,仔細(xì)研究了下每一個(gè)線程的調(diào)用棧,大概卡死在這三個(gè)地方。
<1>. GetModel
public?static?T?GetModel<T,?K>(string?url,?K?content) {T?result?=?default(T);HttpClientHandler?httpClientHandler?=?new?HttpClientHandler();httpClientHandler.AutomaticDecompression?=?DecompressionMethods.GZip;HttpClientHandler?handler?=?httpClientHandler;using?(HttpClient?httpClient?=?new?HttpClient(handler)){string?content2?=?JsonConvert.SerializeObject((object)content);HttpContent?httpContent?=?new?StringContent(content2);httpContent.Headers.ContentType?=?new?MediaTypeHeaderValue("application/json");string?mD5ByCrypt?=?Md5.GetMD5ByCrypt(ConfigurationManager.AppSettings["SsoToken"]?+?DateTime.Now.ToString("yyyyMMdd"));httpClient.DefaultRequestHeaders.Add("token",?mD5ByCrypt);httpClient.DefaultRequestHeaders.Accept.Add(new?MediaTypeWithQualityHeaderValue("application/json"));HttpResponseMessage?result2?=?httpClient.PostAsync(url,?httpContent).Result;if?(result2.IsSuccessStatusCode){string?result3?=?result2.Content.ReadAsStringAsync().Result;return?JsonConvert.DeserializeObject<T>(result3);}return?result;} }<2>. Get
public?static?T?Get<T>(string?url,?string?serviceModuleName) {try{T?val3?=?default(T);HttpClient?httpClient?=?TryGetClient(serviceModuleName,?true);using?(HttpResponseMessage?httpResponseMessage?=?httpClient.GetAsync(GetRelativeRquestUrl(url,?serviceModuleName,?true)).Result){if?(httpResponseMessage.IsSuccessStatusCode){string?result?=?httpResponseMessage.Content.ReadAsStringAsync().Result;if?(!string.IsNullOrEmpty(result)){val3?=?JsonConvert.DeserializeObject<T>(result);}}}T?val4?=?val3;val5?=?val4;return?val5;}catch?(Exception?exception){throw;} }<3>. GetStreamByApi
public?static?Stream?GetStreamByApi<T>(string?url,?T?content) {Stream?result?=?null;HttpClientHandler?httpClientHandler?=?new?HttpClientHandler();httpClientHandler.AutomaticDecompression?=?DecompressionMethods.GZip;HttpClientHandler?handler?=?httpClientHandler;using?(HttpClient?httpClient?=?new?HttpClient(handler)){httpClient.DefaultRequestHeaders.Accept.Add(new?MediaTypeWithQualityHeaderValue("application/octet-stream"));string?content2?=?JsonConvert.SerializeObject((object)content);HttpContent?httpContent?=?new?StringContent(content2);httpContent.Headers.ContentType?=?new?MediaTypeHeaderValue("application/json");HttpResponseMessage?result2?=?httpClient.PostAsync(url,?httpContent).Result;if?(result2.IsSuccessStatusCode){result?=?result2.Content.ReadAsStreamAsync().Result;}httpContent.Dispose();return?result;} }4. 尋找真相
上面我羅列的這三個(gè)方法的代碼,不知道大家可看出什么問題了?對(duì),就是 異步方法同步化,這種寫法本身就很低效,主要表現(xiàn)在2個(gè)方面。
開閉線程本身就是一個(gè)相對(duì)耗費(fèi)資源和低效的操作。
頻繁的線程調(diào)度給了cpu巨大的壓力
而且這種寫法在請(qǐng)求量比較小的情況下還看不出什么問題,一旦請(qǐng)求量稍大一些,馬上就會(huì)遇到該dump的這種情況。
三:總結(jié)
綜合來看這次hangon事故是由于開發(fā)人員 異步方法不會(huì)異步化 導(dǎo)致,改法很簡(jiǎn)單,進(jìn)行純異步化改造 (await,async),解放調(diào)用線程,充分利用驅(qū)動(dòng)設(shè)備的能力。
這個(gè)dump也讓我想起了 CLR Via C# 書中(P646,647) 在講用 await,async 來改造 同步請(qǐng)求?的例子 。
我覺得這個(gè)dump就是該例子的最好佐證!????????????
END
工作中的你,是否已遇到 ...?
1. CPU爆高
2. 內(nèi)存暴漲
3. 資源泄漏
4. 崩潰死鎖
5. 程序呆滯
等緊急事件,全公司都指望著你能解決...? 危難時(shí)刻才能展現(xiàn)你的技術(shù)價(jià)值,作為專注于.NET高級(jí)調(diào)試的技術(shù)博主,歡迎微信搜索: 一線碼農(nóng)聊技術(shù),免費(fèi)協(xié)助你分析Dump文件,希望我能將你的踩坑經(jīng)驗(yàn)分享給更多的人。
總結(jié)
以上是生活随笔為你收集整理的记一次 .NET 某云采购平台API 挂死分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# WPF MVVM项目实战(进阶①)
- 下一篇: 利用 PGO 提升 .NET 程序性能