一:背景

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 方法的 listDeptdeptId 参数给挖出来,我们从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 项的子节点和父节点是循环的,这自然就导致了死循环,所以这次生产事故本质上是数据导致的,将结果反馈给朋友,也得到了确认。

问题找到了之后,解决办法也比较简单。

  1. 校正数据。

  2. 可以设置循环上限,如果超过某个阈值,直接抛出异常,这样可以避免出现CPU爆高导致的机器级别故障

PS: .net core 版本的 Dictionary 就是这么干的,参考代码如下:

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

三:总结

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

记一次 .NET某差旅系统 CPU爆高分析的更多相关文章

  1. 记一次 .NET 某医院HIS系统 CPU爆高分析

    一:背景 1. 讲故事 前几天有位朋友加 wx 抱怨他的程序在高峰期总是莫名其妙的cpu爆高,求助如何分析? 和这位朋友沟通下来,据说这问题困扰了他们几年,还请了微软的工程师过来解决,无疾而终,应该还 ...

  2. 记一次 .NET 某智慧物流 WCS系统 CPU 爆高分析

    一:背景 1. 讲故事 哈哈,再次见到物流类软件,上个月有位朋友找到我,说他的程序出现了 CPU 爆高,让我帮忙看下什么原因,由于那段时间在苦心研究 C++,分析和经验分享也就懈怠了,今天就给大家安排 ...

  3. 记一次 .NET 某电子病历 CPU 爆高分析

    一:背景 1.讲故事 前段时间有位朋友微信找到我,说他的程序出现了 CPU 爆高,帮忙看下程序到底出了什么情况?图就不上了,我们直接进入主题. 二:WinDbg 分析 1. CPU 真的爆高吗? 要确 ...

  4. 记一次 .NET 车联网云端服务 CPU爆高分析

    一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序CPU经常飙满,没找到原因,希望帮忙看一下. 这些天连续接到几个cpu爆高的dump,都看烦了,希望后面再来几个其他方面的dump,从沟通上看, ...

  5. 记一次 .NET 某安全生产信息系统 CPU爆高分析

    一:背景 1.讲故事 今天是的第四天,头终于不巨疼了,写文章已经没什么问题,赶紧爬起来写. 这个月初有位朋友找到我,说他的程序出现了CPU爆高,让我帮忙看下怎么回事,简单分析了下有两点比较有意思. 这 ...

  6. 记一次 .NET 某游戏网站 CPU爆高分析

    一:背景 1. 讲故事 这段时间经常有朋友微信上问我这个真实案例分析连载怎么不往下续了,关注我的朋友应该知道,我近二个月在研究 SQLSERVER,也写了十多篇文章,为什么要研究这东西呢? 是因为在 ...

  7. 记一次 .NET 某资讯论坛 CPU爆高分析

    大概有11天没发文了,真的不是因为懒,本想前几天抽空写,不知道为啥最近求助的朋友比较多,一天都能拿到2-3个求助dump,晚上回来就是一顿分析,有点意思的是大多朋友自己都分析了几遍或者公司多年的牛皮藓 ...

  8. 记一次 .NET 某电商交易平台Web站 CPU爆高分析

    一:背景 1. 讲故事 已经连续写了几篇关于内存暴涨的真实案例,有点麻木了,这篇换个口味,分享一个 CPU爆高 的案例,前段时间有位朋友在 wx 上找到我,说他的一个老项目经常收到 CPU > ...

  9. 记一次 .NET 某机械臂智能机器人控制系统MRS CPU爆高分析

    一:背景 1. 讲故事 这是6月中旬一位朋友加wx求助dump的故事,他的程序 cpu爆高UI卡死,问如何解决,截图如下: 在拿到这个dump后,我发现这是一个关于机械臂的MRS程序,哈哈,在机械臂这 ...

  10. 记一次 .NET 差旅管理后台 CPU 爆高分析

    一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的 web 系统 cpu 运行一段时候后就爆高了,让我帮忙看一下是怎么回事,那就看吧,声明一下,我看 dump 是免费的,主要是锤炼自己技术 ...

随机推荐

  1. 【QT性能优化】QT性能优化之QT6框架高性能网络编程框架实现百万TCP长连接网络服务器:QT是否适合做高性能网络应用?补天云这个视频告诉你在大厂Linux云服务器上的实测结果

    QT性能优化之QT6框架高性能网络编程框架实现百万TCP长连接网络服务器 Ø 简介 本文作者编写了一套基于QT的TCP网络服务器程序和基于QT的TCP客户端程序,在某大厂的云服务器上进行了C1000K ...

  2. SaaS业务架构:业务能力分析

    大家好,我是汤师爷~ 今天聊聊SaaS业务架构的业务能力分析. 业务能力概述 简单来说,业务能力是企业"做某事的能力". 业务能力描述了企业当前和未来应对挑战的能力,即企业能做什么 ...

  3. 使用GRUB Multiboot2引导自制操作系统

    使用GRUB Multiboot2引导自制操作系统 前言 之前花了一周时间,从头学习了传统 BIOS 的启动流程.惊讶于背后丰富的技术细节的同时,也感叹 x86 架构那厚重的历史包袱.毕竟,谁能想到, ...

  4. Android Systrace 基础知识 -- Why 60 fps ?

    1.正文 今天来讲一下为何我们讲到流畅度,要首先说 60 帧. 我们先来理一下基本的概念: 60 fps 的意思是说,画面每秒更新 60 次 这 60 次更新,是要均匀更新的,不是说一会快,一会慢,那 ...

  5. git工具:sourcetree使用中的部分问题

    这段时间经常用到这个工具.就当记个笔记,记录一下我的一些问题. 问题一: 如果想要拉取远端更新: 第一步:先登陆sourcetree,点击"抓取". 第二步:在终端输入:git s ...

  6. .NET 实现的交互式 OA 系统

    前言 近期,我们在后台收到了粉丝们的留言,需要一个高效办公自动化(OA)系统.为了回应大家的期待,今天我们推荐一款既灵活又强大的 OA 系统解决方案,帮助提升日常办公效率和团队协作水平. 在日常工作中 ...

  7. 各位Oracle DBA们,你们期待的在线实训环境终于来了

    各位Oracle DBA,你们是否曾在学习.工作时遇到不得不上正式库测试的情况,如果没事发生那一切岁月静好,但若一不小心删了用户.删了表甚至删了库,可就是悲剧了--从做出开库测试这个决定起,胆战心惊就 ...

  8. html5新标签 画布 canvas 替代了 flash

    绘制矩形边框,和填充不同的是绘制使用的是strokeRect, 和strokeStyle实现的 绘制路径 绘制路径的作用是为了设置一个不规则的多边形状态 路径都是闭合的,使用路径进行绘制的时候需要既定 ...

  9. 我们在 vue 项目中如何做路由导航守卫

    一般在 src 文件夹新建一个 permission 文件 ,单独用来做路由导航守卫业务 ,在 main.js 导入文件即可 : 主要功能有 判断是否有 token ,以此判断用户是不是登录了 :

  10. vue打包后,添加入spring boot下,访问不到字体的BUG

    主要报错:OTS parsing error: incorrect file size in WOFF header OTS parsing error: incorrect entrySelecto ...