一:背景

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

记一次 .NET 某云采购平台API 挂死分析的更多相关文章

  1. 记一次 .NET 某纺织工厂 MES系统 API 挂死分析

    一:背景 1. 讲故事 这个月中旬,有位朋友加我wx求助他的程序线程占有率很高,寻求如何解决,截图如下: 说实话,和不同行业的程序员聊天还是蛮有意思的,广交朋友,也能扩大自己的圈子,朋友说他因为这个b ...

  2. 记一次 .NET WPF布草管理系统 挂死分析

    一:背景 1. 讲故事 这几天看的 dump 有点多,有点伤神伤脑,晚上做梦都是dump,今天早上头晕晕的到公司就听到背后同事抱怨他负责的WPF程序挂死了,然后测试的小姑娘也跟着抱怨...嗨,也不知道 ...

  3. 记一次 .NET 某新能源汽车锂电池检测程序 UI挂死分析

    更多高质量干货:参见我的 GitHub: dotnetfly 一:背景 1. 讲故事 这世间事说来也奇怪,近两个月有三位朋友找到我,让我帮忙分析下他的程序hangon现象,这三个dump分别涉及: 医 ...

  4. Bmob移动后端云服务平台--Android从零開始--(一)何为Bmob

    Bmob移动后端云服务平台--Android从零開始--(一)何为Bmob 在正式的项目开发中,单client不能满足我们的需求,须要实现client与服务端的连接. 而在编写Android服务端代码 ...

  5. 灵魂画师,在线科普多云平台/CMP云管平台/中间件/虚拟化/容器是个啥

    原创: 灵魂工作室 速石科技 经常碰到有人问: 你们是云管吗? 你们和CMP多云管理平台有什么区别? 你们这个多云平台到底是个啥? emmmmm,问题还挺不好回答. 为了说清楚这些问题,但又不希望你们 ...

  6. 听云数据库管理平台NetopGO简介

    ➠更多技术干货请戳:听云博客 断断续续写了将近一个月,听云第一版数据库管理平台终于写完了,期间来来回回的改了好多次小毛病,现在已经部署到生产环境上去了. 在刚开始的时候,后端的数据库集群只有10多个节 ...

  7. OpenStack 企业私有云的若干需求(9): 云管理平台 CMP

    本系列会介绍OpenStack 企业私有云的几个需求: 自动扩展(Auto-scaling)支持 多租户和租户隔离 (multi-tenancy and tenancy isolation) 混合云( ...

  8. Testin云测试平台初体验

    这几天偶然接触到了一个叫做Testin的云测试平台,经过一番体验,感觉还是不错的,因为里面提供了大量的测试机型,可以针对Android手机的严重碎片化现象做出比较全面的测试,同时Testin的测试内容 ...

  9. OneAlert:国内首家 SaaS 模式的云告警平台

    随着互联网行业的高速发展,企业 IT 应用环境日趋复杂.几分钟的故障就会严重到用户体验,那么如何有效降低IT故障带来的影响呢?权威数据表明,86%的企业认为建立有效的监控系统和告警系统.提升 IT 的 ...

随机推荐

  1. java后端知识点梳理——Spring

    开篇:感谢我是祖国的花朵,java3y,三太子敖丙等优秀博主!他们的文章为我学习java提供了莫大的帮助,膜拜大神! Spring的优点有哪些呢? Spring的依赖注入将对象之间的依赖关系交给了框架 ...

  2. 重新整理 .net core 实践篇—————异常中间件[二十]

    前言 简单介绍一下异常中间件的使用. 正文 if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } 这样写入中间件哈,那么在env环 ...

  3. 【NX二次开发】Block UI NXOpen::BlockStyler::BlockDialog

    定义: NXOpen::BlockStyler::BlockDialog* theDialog; theDialog->PerformApply();//执行应用并重新启动对话框. theDia ...

  4. 利用 Bean Validation 来简化接口请求参数校验

    团队新来了个校招实习生静静,相互交流后发现竟然是我母校同实验室的小学妹,小学妹很热情地认下了我这个失散多年的大湿哥,后来... 小学妹:大湿哥,咱们项目里的 Controller 怎么都看不到参数校验 ...

  5. Netty 面试题 (史上最全、持续更新)

    文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...

  6. 数位dp从会打模板到不会打模板

    打了几个数位$dp$,发现自己除了会打模板之外没有任何长进,遇到非模板题依然什么都不会 那么接下来这篇文章将介绍如何打模板(滑稽) 假设我们要处理$l----r$ 采用记忆化搜索的方式,枚举$< ...

  7. 办公利器!用Python批量识别发票并录入到Excel表格

    辰哥今天来分享一篇办公干货文章:用Python批量识别发票并录入到Excel表格.对于财务专业等学生或者公司财务人员来说,将报账发票等汇总到excel简直就是一个折磨. 尤其是到年底的时候,公司的财务 ...

  8. 用python的matplotlib根据文件里面的数字画图像折线图

    思路:用open打开文件,再用a=filename.readlines()提取每行的数据作为列表的值,然后传递列表给matplotlib并引入对应库画出图像 代码实现:import matplotli ...

  9. EXCEL根据某一列单元格特定标点符号分行,其它列内容一致

    注意事项:此方法注意运用excel空值单元格填充,因此数据处理之前,如果存在值为空的,请特殊处理,后期处理完再替换为原来的空值 1.在决定分行数的列后插入一列 2.根据逗号之前值最多的数量来决定分行数 ...

  10. MySQL数据库企业集群项目实战(阶段三)

                              MySQL数据库企业集群项目实战(阶段三) 作者 刘畅 时间 2020-10-25 目录 1 架构拓扑图 1 1.1 方案一 1 1.2 方案二 2 ...