共计 4177 个字符,预计需要花费 11 分钟才能阅读完成。
一:背景
1. 讲故事
大略有两个月没写博客了,关注我的敌人应该晓得我最近都把精力花在了星球,这两个月工夫也陆陆续续的有敌人求助如何剖析 dump,有些敌人太客气了,给了大大的红包,哈哈😅,手外面也攒了 10 多个不同问题类型的 dump,后续也会逐个将剖析思路奉献进去。
这个 dump 是一位敌人大略一个月前提供应我的,因为 wx 外面求助的敌人比拟多,一时也没找到相干截图,不得已毁坏一下老规矩。😭😭😭
既然敌人说 api 接口无响应,出现了 hangon 景象,从一些过往教训看,大略也只有三种状况。
- 大量锁期待
- 线程不够用
- 死锁
有了这种先入为主的思维,那就上 windbg 说事呗。
二:windbg 剖析
1. 有大量锁期待吗?
要想看是否锁期待,老规矩,看一下 同步块表
。
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)
处,不过转念一想,就算有 339 个线程卡在这里,真的会导致程序 hangon 吗?也不肯定,毕竟我看过有 1000+ 的线程也不会卡死,只不过 cpu 爆高而已,接下来持续研判一下是不是线程不够用导致,能够从 线程池工作队列
下面动手。
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 位客人在期待,综合这两点信息就曾经很分明了,本次 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