记一次 .NET某差旅系统 CPU爆高分析
一:背景
1. 讲故事
前些天训练营里的一位学员找到我,说他们的差旅后台系统出现了CPU爆高的情况,爆高之后就下不去了,自己分析了下也没找到原因,事情比较紧急,让我帮忙看下是什么回事,手里也有dump,丢过我之后我们上 windbg 分析吧。
二:WinDbg分析
1. 为什么会CPU爆高
看过这个系列的朋友都知道CPU是否爆高,可以用 !tp 命令来验证,参考输出如下:
0:000> !tp
CPU utilization: 100%
Worker Thread: Total: 66 Running: 66 Idle: 0 MaxLimit: 32767 MinLimit: 4
Work Request in Queue: 93
Unknown Function: 00007ffc744f1750 Context: 000002c3acdad7d8
AsyncTimerCallbackCompletion TimerInfo@000002bf57193d20
Unknown Function: 00007ffc744f1750 Context: 000002c3acb2aef0
...
Unknown Function: 00007ffc744f1750 Context: 000002c3ad336718
--------------------------------------
Number of Timers: 0
--------------------------------------
Completion Port Thread:Total: 1 Free: 1 MaxFree: 8 CurrentLimit: 1 MaxLimit: 1000 MinLimit: 4
从卦中的 CPU utilization: 100% 可以确认此时CPU被打满了,同时也有一个现象就是这个线程池队列有堆积的情况,一般来说堆积就表示下游处理不力,常常表现为程序卡死,Http超时,和 CPU爆高问题 没有直接关系,这是一个经验问题,大家一定要甄别,以免陷入误区。
接下来我们看下这台机器的CPU能力如何,能力越弱越容易被打爆,可以用 !cpuid 观察。
0:000> !cpuid
CP F/M/S Manufacturer MHz
0 6,85,7 <unavailable> 2095
1 6,85,7 <unavailable> 2095
2 6,85,7 <unavailable> 2095
3 6,85,7 <unavailable> 2095
从卦中可以看到当前的 CPU=4core,只要4个满负荷的Thread就可以轻松打爆,接下来我们的研究方向在哪里呢?对,就是从 Thread 入手。
2. 线程都在干什么
要想看线程都在干什么?可以使用 ~*e !clrstack 观察即可,参考输出如下:
OS Thread Id: 0x968 (87)
Child SP IP Call Site
000000e63babb9a8 00007ffc879a6974 [GCFrame: 000000e63babb9a8]
000000e63babba78 00007ffc879a6974 [HelperMethodFrame_1OBJ: 000000e63babba78] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)
000000e63babbb90 00007ffc5e735bbf System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken) [f:\dd\ndp\clr\src\BCL\system\threading\ManualResetEventSlim.cs @ 669]
000000e63babbc20 00007ffc5e72e9c5 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 3320]
000000e63babbc90 00007ffc5f0cc188 System.Threading.Tasks.Task.InternalWait(Int32, System.Threading.CancellationToken) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 3259]
000000e63babbd60 00007ffc5f0c9176 System.Threading.Tasks.Task`1[[System.__Canon, mscorlib]].GetResultCore(Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Future.cs @ 559]
000000e63babbda0 00007ffc1b98f2cf xxxCheckFilterAttribute.GetRequestParameter(System.Web.Http.Controllers.HttpActionContext)
...
从卦中可以看到大量的 Wait 等待,其实就是代码中调用 Task.Result 所致,即异步中混合同步,虽然这是一个可能导致线程饥饿的问题,但和我们的目标:CPU爆高无关,所以我们需要在茫茫调用栈中寻找其他可能导致的 CPU 爆高线程,经过仔细而耐心的查找,终于找到了疑似调用栈,刚好有5个都停留在这个位置。参考如下:
OS Thread Id: 0x26a8 (35)
Child SP IP Call Site
000000e62537b048 00007ffc879a64a4 [HelperMethodFrame: 000000e62537b048]
000000e62537b190 00007ffc5e68e04a System.String.Concat(System.String, System.String) [f:\dd\ndp\clr\src\BCL\system\string.cs @ 3207]
000000e62537b1e0 00007ffc1dbe85eb xxx.GetParentDeptName_All(System.Collections.Generic.List`1<xxx.xxxDepts>, Int64)
...
000000e62537c870 00007ffc1e0af75b DynamicClass.lambda_method(System.Runtime.CompilerServices.Closure, System.Object, System.Object[])
000000e62537c8b0 00007ffc1b29b3b8 System.Web.Http.Controllers.ReflectedHttpActionDescriptor+ActionExecutor+c__DisplayClass10.b__9(System.Object, System.Object[])
000000e62537c8f0 00007ffc1b29a768 System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(System.Web.Http.Controllers.HttpControllerContext, System.Collections.Generic.IDictionary`2<System.String,System.Object>, System.Threading.CancellationToken)
000000e62537c950 00007ffc1b29a18e System.Web.Http.Controllers.ApiControllerActionInvoker+d__0.MoveNext()
000000e62537c9c0 00007ffc1b2996ca System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[[System.__Canon, mscorlib]].Start[[System.Web.Http.Controllers.ApiControllerActionInvoker+d__0, System.Web.Http]](d__0 ByRef)
000000e62537ca70 00007ffc1b299611 System.Web.Http.Controllers.ApiControllerActionInvoker.InvokeActionAsyncCore(System.Web.Http.Controllers.HttpActionContext, System.Threading.CancellationToken)
000000e62537cb30 00007ffc1b299535 System.Web.Http.Controllers.ApiControllerActionInvoker.InvokeActionAsync(System.Web.Http.Controllers.HttpActionContext, System.Threading.CancellationToken)
000000e62537cb60 00007ffc1b299504 System.Web.Http.Controllers.ActionFilterResult.b__0(ActionInvoker)
000000e62537cb90 00007ffc1b2994a9 System.Web.Http.Controllers.ActionFilterResult+c__DisplayClass10`1[[System.Web.Http.Controllers.ActionFilterResult+ActionInvoker, System.Web.Http]].b__f()
000000e62537cbe0 00007ffc1b2622f9 System.Web.Http.Filters.ActionFilterAttribute+d__5.MoveNext()
000000e62537cc40 00007ffc1b261cfa System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[[System.__Canon, mscorlib]].Start[[System.Web.Http.Filters.ActionFilterAttribute+d__5, System.Web.Http]](d__5 ByRef)
000000e62537ccf0 00007ffc1b261bf4 System.Web.Http.Filters.ActionFilterAttribute.CallOnActionExecutedAsync(System.Web.Http.Controllers.HttpActionContext, System.Threading.CancellationToken, System.Func`1<System.Threading.Tasks.Task`1<System.Net.Http.HttpResponseMessage>>)
000000e62537cdd0 00007ffc1b2584d4 System.Web.Http.Filters.ActionFilterAttribute+d__0.MoveNext()

到了这里之后,甭管有没有问题,反正嫌疑很大,接下来就得研究 GetParentDeptName_All 方法。
3. GetParentDeptName_All 有问题吗
要想知道有没有问题,我们用 ILSpy观察其源码,由于客户隐私的问题,这里就稍微模糊一下,截图如下:

从卦中看,尼玛,还真有一个 while 逻辑,一下子就提起了我的兴趣,看样子八九不离十,接下来到线程栈上 GetParentDeptName_All 方法的 listDept 和 deptId 参数给挖出来,我们从35号线程入手,使用 !clrstack -a 参数观察输出结果。
0:035> !clrstack -a
OS Thread Id: 0x26a8 (35)
Child SP IP Call Site
000000e62537b048 00007ffc879a64a4 [HelperMethodFrame: 000000e62537b048]
000000e62537b190 00007ffc5e68e04a System.String.Concat(System.String, System.String) [f:\dd\ndp\clr\src\BCL\system\string.cs @ 3207]
PARAMETERS:
str0 (<CLR reg>) = 0x000002c05816f360
str1 (<CLR reg>) = 0x000002c3e21eaf88
LOCALS:
<no data>
<no data>
000000e62537b1e0 00007ffc1dbe85eb xxx.GetParentDeptName_All(System.Collections.Generic.List`1<xxxDepts>, Int64)
PARAMETERS:
this (0x000000e62537b2c0) = 0x000002c059898590
listDept (0x000000e62537b2c8) = 0x000002c159803390
deptId (0x000000e62537b2d0) = 0x00000000000324fb
拿到了 listDept 的地址之后,用 .logopen 把 !mdt -e:2 000002c159803390 的输出结果全部记录到文本文件,为了保护客户隐私,这里就不截图了,直接上文本。
[20828] 000002c059802b68 (xxxtDepts)
<DeptId>k__BackingField:0x324fb (System.Int32)
<ParentDeptId>k__BackingField:0x31347 (System.Int32)
...
[20643] 000002c0597f0e30 (xxxtDepts)
<DeptId>k__BackingField:0x2f240 (System.Int32)
<ParentDeptId>k__BackingField:0x31347 (System.Int32)
...
[20663] 000002c0597f2d18 (xxxtDepts)
<DeptId>k__BackingField:0x2f254 (System.Int32)
<ParentDeptId>k__BackingField:0x2f240 (System.Int32)
...
从卦中数据看,listDept数组的第 20643 和 20663 项的子节点和父节点是循环的,这自然就导致了死循环,所以这次生产事故本质上是数据导致的,将结果反馈给朋友,也得到了确认。

问题找到了之后,解决办法也比较简单。
校正数据。
可以设置循环上限,如果超过某个阈值,直接抛出异常,这样可以避免出现CPU爆高导致的机器级别故障
PS: .net core 版本的 Dictionary 就是这么干的,参考代码如下:

在 .net framework 中就会出现傻乎乎打爆的严重事件。

三:总结
我的学员没有分析出来,我觉得应该是被 Task.Result 给误导了,真实的dump分析可能会真真假假,假假真真,就像这个社会一样,需要更多的实践历练吧。

记一次 .NET某差旅系统 CPU爆高分析的更多相关文章
- 记一次 .NET 某医院HIS系统 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友加 wx 抱怨他的程序在高峰期总是莫名其妙的cpu爆高,求助如何分析? 和这位朋友沟通下来,据说这问题困扰了他们几年,还请了微软的工程师过来解决,无疾而终,应该还 ...
- 记一次 .NET 某智慧物流 WCS系统 CPU 爆高分析
一:背景 1. 讲故事 哈哈,再次见到物流类软件,上个月有位朋友找到我,说他的程序出现了 CPU 爆高,让我帮忙看下什么原因,由于那段时间在苦心研究 C++,分析和经验分享也就懈怠了,今天就给大家安排 ...
- 记一次 .NET 某电子病历 CPU 爆高分析
一:背景 1.讲故事 前段时间有位朋友微信找到我,说他的程序出现了 CPU 爆高,帮忙看下程序到底出了什么情况?图就不上了,我们直接进入主题. 二:WinDbg 分析 1. CPU 真的爆高吗? 要确 ...
- 记一次 .NET 车联网云端服务 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序CPU经常飙满,没找到原因,希望帮忙看一下. 这些天连续接到几个cpu爆高的dump,都看烦了,希望后面再来几个其他方面的dump,从沟通上看, ...
- 记一次 .NET 某安全生产信息系统 CPU爆高分析
一:背景 1.讲故事 今天是的第四天,头终于不巨疼了,写文章已经没什么问题,赶紧爬起来写. 这个月初有位朋友找到我,说他的程序出现了CPU爆高,让我帮忙看下怎么回事,简单分析了下有两点比较有意思. 这 ...
- 记一次 .NET 某游戏网站 CPU爆高分析
一:背景 1. 讲故事 这段时间经常有朋友微信上问我这个真实案例分析连载怎么不往下续了,关注我的朋友应该知道,我近二个月在研究 SQLSERVER,也写了十多篇文章,为什么要研究这东西呢? 是因为在 ...
- 记一次 .NET 某资讯论坛 CPU爆高分析
大概有11天没发文了,真的不是因为懒,本想前几天抽空写,不知道为啥最近求助的朋友比较多,一天都能拿到2-3个求助dump,晚上回来就是一顿分析,有点意思的是大多朋友自己都分析了几遍或者公司多年的牛皮藓 ...
- 记一次 .NET 某电商交易平台Web站 CPU爆高分析
一:背景 1. 讲故事 已经连续写了几篇关于内存暴涨的真实案例,有点麻木了,这篇换个口味,分享一个 CPU爆高 的案例,前段时间有位朋友在 wx 上找到我,说他的一个老项目经常收到 CPU > ...
- 记一次 .NET 某机械臂智能机器人控制系统MRS CPU爆高分析
一:背景 1. 讲故事 这是6月中旬一位朋友加wx求助dump的故事,他的程序 cpu爆高UI卡死,问如何解决,截图如下: 在拿到这个dump后,我发现这是一个关于机械臂的MRS程序,哈哈,在机械臂这 ...
- 记一次 .NET 差旅管理后台 CPU 爆高分析
一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的 web 系统 cpu 运行一段时候后就爆高了,让我帮忙看一下是怎么回事,那就看吧,声明一下,我看 dump 是免费的,主要是锤炼自己技术 ...
随机推荐
- 服务器文件打压缩包下载(java)
public void download(HttpServletRequest request, HttpServletResponse response){ try { String downloa ...
- 面试官:谈谈你对 IoC 和 AOP 的理解!
本文摘录自笔者开源的 Java 学习&面试指南(Github 收获146k star):JavaGuide . 这篇文章会从下面从以下几个问题展开对 IoC & AOP 的解释 什么是 ...
- 揭秘!尤雨溪成立的VoidZero如何改变前端世界
前言 Vue和Vite之父尤雨溪宣布成立公司 VoidZero,目前已经融资3200万.这篇文章欧阳将带你了解VoidZero是如何改变javascript的世界! 关注公众号:[前端欧阳],给自己一 ...
- USB总线-Linux内核USB3.0设备控制器中断处理程序分析(九)
1.概述 USB设备枚举.请求处理.数据交互都涉及USB设备控制器中断.当有事件发生时,USB设备控制器首先将事件信息通过DMA写入到事件缓冲区中,然后向CPU发出中断,随后CPU调用中断处理函数开始 ...
- 深度学习卷积、全连接层、深度可分离层参数量和FLOPs计算公式
来源 卷积: 输入尺寸 ,卷积核的大小为 * ,输出的尺寸大小为 参数量 (1)不考虑bias:(2)考虑bias: FLOPs (1)不考虑bias 解释:先计算输出的中一个元素需要的计算量, ...
- pytorch: grad can be implicitly created only for scalar outputs
运行这段代码 import torch import numpy as np import matplotlib.pyplot as plt x = torch.ones(2,2,requires_g ...
- google 搜索技巧
size qq.com inurl : size 指定网站 inurl 这个网站的指定内容 filetype 指定搜索的文件类型
- kotlin更多语言结构——>解构声明
解构声明 有时把一个对象 解构 成很多变量会很方便,例如: val (name, age) = person 这种语法称为 解构声明 .一个解构声明同时创建多个变量.我们已经声明了两个新变量:name ...
- 使用nacos上传配置文件报错
1.使用nacos导入配置文件报错:未读取到合法数据,请检查导入的数据文件. 对比在naocs server中导出的文件,发现是少了一级目录.需要创建一个文件夹,名称为组的名称.因为在nacos上传文 ...
- 快速搭建hadoop,zk,hbase的基础集群
1. ZK集群,Hadoop集群,Hbase集群安装 Linux121 Linux122 Linux123 Hadoop MySQL ZK HBASE 1.1 安装Vmware,安装虚拟机集群 1.1 ...