关于.net:记一次-NET-某云采购平台API-挂死分析

2次阅读

共计 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

正文完
 0