一:背景
1. 讲故事
大略有两个月没写博客了,关注我的敌人应该晓得我最近都把精力花在了星球,这两个月工夫也陆陆续续的有敌人求助如何剖析dump,有些敌人太客气了,给了大大的红包,哈哈,手外面也攒了10多个不同问题类型的dump,后续也会逐个将剖析思路奉献进去。
这个dump是一位敌人大略一个月前提供应我的,因为wx外面求助的敌人比拟多,一时也没找到相干截图,不得已毁坏一下老规矩。
既然敌人说api接口无响应,出现了hangon景象,从一些过往教训看,大略也只有三种状况。
- 大量锁期待
- 线程不够用
- 死锁
有了这种先入为主的思维,那就上windbg说事呗。
二: windbg 剖析
1. 有大量锁期待吗?
要想看是否锁期待,老规矩,看一下 同步块表
。
0:000> !syncblkIndex SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner-----------------------------Total 1673CCW 3RCW 4ComClassFactory 0Free 397
扑了个空,啥也没有,那就暴力看看所有的线程栈吧。
不看还好,一看吓一跳,有339个线程卡在了 System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)
处,不过转念一想,就算有339个线程卡在这里,真的会导致程序hangon吗? 也不肯定,毕竟我看过有1000+的线程也不会卡死,只不过cpu爆高而已,接下来持续研判一下是不是线程不够用导致,能够从 线程池工作队列
下面动手。
2. 探索线程池队列
能够用 !tp
命令查看。
0:000> !tpCPU utilization: 10%Worker Thread: Total: 328 Running: 328 Idle: 0 MaxLimit: 32767 MinLimit: 4Work 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位客人在期待,综合这两点信息就曾经很分明了,本次hangon是因为大量的客人到来超出了线程池的接待能力所致。
3. 接待能力真的不行吗?
这个题目我感觉很好,真的不行吗? 到底行不行,能够从两点动手:
- 是不是代码写的烂?
- qps是不是真的超出了接待能力?
要想找出答案,还得从那 339 个卡死的线程说起,认真钻研了下每一个线程的调用栈,大略卡死在这三个中央。
<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. 寻找假相
下面我列举的这三个办法的代码,不晓得大家可看出什么问题了? 对,就是 异步办法同步化
,这种写法自身就很低效,次要体现在2个方面。
- 开闭线程自身就是一个绝对消耗资源和低效的操作。
- 频繁的线程调度给了cpu微小的压力
而且这种写法在申请量比拟小的状况下还看不出什么问题,一旦申请量稍大一些,马上就会遇到该dump的这种状况。
三:总结
综合来看这次hangon事变是因为开发人员 异步办法不会异步化
导致,改法很简略,进行纯异步化革新 (await,async),解放调用线程,充分利用驱动设施的能力。
这个dump也让我想起了 CLR Via C#
书中(P646,647) 在讲用 await,async 来革新 同步申请 的例子 。
我感觉这个dump就是该例子的最好佐证!
更多高质量干货:参见我的 GitHub: dotnetfly