.NET 某云采購平臺API 掛死問題解析
一:背景
1. 講故事
大概有兩個月沒寫博客了,關(guān)注我的朋友應該知道我最近都把精力花在了星球,這兩個月時間也陸陸續(xù)續(xù)的有朋友求助如何分析dump,有些朋友太客氣了,給了大大的紅包,哈哈??,手里面也攢了10多個不同問題類型的dump,后續(xù)也會逐一將分析思路貢獻出來。
這個dump是一位朋友大概一個月前提供給我的,由于wx里面求助的朋友比較多,一時也沒找到相關(guān)截圖,不得已破壞一下老規(guī)矩。??????
既然朋友說api接口無響應,呈現(xiàn)了hangon現(xiàn)象,從一些過往經(jīng)驗看,大概也只有三種情況。
大量鎖等待
線程不夠用
死鎖
有了這種先入為主的思想,那就上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
撲了個空,啥也沒有,那就暴力看看所有的線程棧吧。
不看還好,一看嚇一跳,有339個線程卡在了 System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)
處,不過轉(zhuǎn)念一想,就算有339個線程卡在這里,真的會導致程序hangon嗎? 也不一定,畢竟我看過有1000+的線程也不會卡死,只不過cpu爆高而已,接下來繼續(xù)研判一下是不是線程不夠用導致,可以從 線程池任務隊列
上面入手。
2. 探究線程池隊列
可以用 !tp
命令查看。
0:000> !tp CPU utilization: 10% Worker Thread: Total: 328 Running: 328 Idle: 0 MaxLimit: 32767 MinLimit: 4 Work Request in Queue: 74 Unknown Function: 00007ffe91cc17d0 Context: 000001938b5d8d98 Unknown Function: 00007ffe91cc17d0 Context: 000001938b540238 Unknown Function: 00007ffe91cc17d0 Context: 000001938b5eec08 ... Unknown Function: 00007ffe91cc17d0 Context: 0000019390552948 Unknown Function: 00007ffe91cc17d0 Context: 0000019390562398 Unknown Function: 00007ffe91cc17d0 Context: 0000019390555b30 -------------------------------------- Number of Timers: 0 -------------------------------------- Completion Port Thread:Total: 5 Free: 4 MaxFree: 8 CurrentLimit: 4 MaxLimit: 1000 MinLimit: 4
從輸出信息看,線程池中328個線程全部打滿,工作隊列中還有74位客人在等待,綜合這兩點信息就已經(jīng)很清楚了,本次hangon是由于大量的客人到來超出了線程池的接待能力所致。
3. 接待能力真的不行嗎?
這個標題我覺得很好,真的不行嗎? 到底行不行,可以從兩點入手:
是不是代碼寫的爛?
qps是不是真的超出了接待能力?
要想找出答案,還得從那 339 個卡死的線程說起,仔細研究了下每一個線程的調(diào)用棧,大概卡死在這三個地方。
<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. 尋找真相
上面我羅列的這三個方法的代碼,不知道大家可看出什么問題了? 對,就是 異步方法同步化
,這種寫法本身就很低效,主要表現(xiàn)在2個方面。
開閉線程本身就是一個相對耗費資源和低效的操作。
頻繁的線程調(diào)度給了cpu巨大的壓力
而且這種寫法在請求量比較小的情況下還看不出什么問題,一旦請求量稍大一些,馬上就會遇到該dump的這種情況。
三:總結(jié)
綜合來看這次hangon事故是由于開發(fā)人員 異步方法不會異步化
導致,改法很簡單,進行純異步化改造 (await,async),解放調(diào)用線程,充分利用驅(qū)動設備的能力。
這個dump也讓我想起了 CLR Via C#
書中(P646,647) 在講用 await,async 來改造 同步請求 的例子 。
到此這篇關(guān)于.NET 某云采購平臺API 掛死問題解析的文章就介紹到這了,更多相關(guān).NET 某云采購平臺API內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#使用RichTextBox實現(xiàn)替換文字及改變字體顏色功能示例
這篇文章主要介紹了C#使用RichTextBox實現(xiàn)替換文字及改變字體顏色功能,結(jié)合實例形式洗了C#中RichTextBox組件文字替換及改變字體顏色相關(guān)操作技巧,需要的朋友可以參考下2019-02-02C#中將ListView中數(shù)據(jù)導出到Excel的實例方法
首先 你需要添加引用Microsoft Excel 11.0 Object Library2013-04-04