一:背景

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