记一次 .NET 某医保平台 CPU 爆高分析
一:背景
1. 讲故事
一直在追这个系列的朋友应该能感受到,我给这个行业中无数的陌生人分析过各种dump,终于在上周有位老同学找到我,还是个大妹子,必须有求必应 。
妹子公司的系统最近在某次升级之后,在高峰期会遇到 CPU 爆高的现象,有些单位你懂的,很强势,所以就苦逼了程序媛,不管怎么说,既然找上我,得想各种办法给解决掉,用远程的方式告诉了老同学怎么用 procdump 去抓 dump,在一个小时之后 dump 成功拿到,接下来就来分析了。
二:WinDbg 分析
1. CPU 真的爆高吗
是不是爆高一定要拿数据说话,可以用 !tp 命令验证。
0:234> !tp
CPU utilization: 44%
Worker Thread: Total: 68 Running: 38 Idle: 10 MaxLimit: 4800 MinLimit: 48
Work Request in Queue: 0
--------------------------------------
Number of Timers: 2
--------------------------------------
Completion Port Thread:Total: 1 Free: 1 MaxFree: 96 CurrentLimit: 1 MaxLimit: 4800 MinLimit: 48
从输出看 CPU=44% 就拿到了 dump ,真的有点奇怪,我设置的阈值是 70% ,可能是老同学后来改了阈值,虽然不是我的预期但还是可以探究一下的。
接下来怎么排查呢?我们都知道 CPU 高本质都是由 线程 造成的,所以到每个线程栈上去兜兜,看看有没有什么收获,使用命令 ~*e !clrstack 即可。
0:234> ~*e !clrstack
OS Thread Id: 0x6de8 (234)
Child SP IP Call Site
000000959273dfd8 00007ff89d287174 [HelperMethodFrame_2OBJ: 000000959273dfd8] System.Security.Cryptography.Utils._AcquireCSP(System.Security.Cryptography.CspParameters, System.Security.Cryptography.SafeProvHandle ByRef)
000000959273e0d0 00007ff88af3074e System.Security.Cryptography.Utils.AcquireProvHandle(System.Security.Cryptography.CspParameters)
000000959273e110 00007ff88b85e585 System.Security.Cryptography.CryptoAPITransform..ctor(Int32, Int32, Int32[], System.Object[], Byte[], System.Security.Cryptography.PaddingMode, System.Security.Cryptography.CipherMode, Int32, Int32, Boolean, System.Security.Cryptography.CryptoAPITransformMode)
000000959273e230 00007ff88b86057d System.Security.Cryptography.DESCryptoServiceProvider._NewEncryptor(Byte[], System.Security.Cryptography.CipherMode, Byte[], Int32, System.Security.Cryptography.CryptoAPITransformMode)
000000959273e2e0 00007ff88b8602f3 System.Security.Cryptography.DESCryptoServiceProvider.CreateDecryptor(Byte[], Byte[])
0000009588e7e300 00007ff82f7bd49a xxx.DesDecrypt(System.String)
0000009588e7e370 00007ff82f9848e2 xxx.b__17(xxx_UserInfo)
0000009588e7e3a0 00007ff887d7778e System.Linq.Enumerable+WhereListIterator`1[[System.__Canon, mscorlib]].MoveNext()
0000009588e7e3e0 00007ff887d7767e System.Linq.Enumerable.FirstOrDefault[[System.__Canon, mscorlib]](System.Collections.Generic.IEnumerable`1<System.__Canon>)
...

从输出以及截图来看,有 38 个线程正在执行 xxx.DesDecrypt 方法,看起来是一个 DES 解密操作,一般来说 加解密 比较费CPU资源,看样子找到问题根了,根据线程栈找一下代码到底是怎么写的,为了保密起见,这里就多用几个 XXX 来替代吧,参考代码如下:
protected void xxx(string xxx)
{
xxx userInfo = xxxUserInfoList.Where((xxx p) => p.xxx.Name == xxx.DesDecrypt(base.User.Name)).FirstOrDefault();
}
说实话第一眼看到在 Where 中使用 DesDecrypt(base.User.Name) 方法,虽然有点别扭,内心还是感觉扩展方法应该会帮我优化把它单独给提出来的,比如下面这样。
protected void xxx(string xxx)
{
var nameWhere= xxx.DesDecrypt(base.User.Name);
xxx userInfo = xxxUserInfoList.Where((xxx p) => p.xxx.Name == nameWhere).FirstOrDefault();
}
但看了线程栈上的 WhereListIterator 和 FirstOrDefault 方法,貌似没有做优化,为了验证我的想法,我还特意写了段代码。
internal class Program
{
static void Main(string[] args)
{
var query = new List<string>() { "1", "2","3","4" };
var text = "hello world";
query.Where(i => i == Run(text)).FirstOrDefault();
}
public static string Run(string str)
{
Console.WriteLine(str);
return str;
}
}

从输出结果看 hello world 输出了 4 次,也就表明当前并没有做任何优化。
再说点题外话,最近在研究 SQLSERVER,我觉得它的 SQL优化器应该能够处理这种情况,截图如下:
SELECT OrderID FROM dbo.Orders
WHERE OrderDate > SUBSTRING('1996-07-10 00:00:10.000', 0, 11) AND
OrderDate < SUBSTRING('1996-07-20 00:00:20.000', 0, 11);

虽然 SQLSERVER 非常智能的做了优化,但后来想一想C#不优化是对的,因为框架代码没法保证向 xxx.DesDecrypt 方法中传入相同参数,返回的结果一定是相同的,所以采用保守的方法能够理解。
2. 解密操作真的会爆高吗
就算WHERE中逐项处理解密操作就一定会爆高吗?这是一个需要求证的问题,可以切到某个线程上用 !dso 找出那个 list,然后 !do 即可,参考代码如下:
0:234> !DumpObj /d 000001f631799240
Name: System.Collections.Generic.List`1[[xxxx_UserInfo,xxxx]]
MethodTable: 00007ff82f5f3b20
EEClass: 00007ff88ab59f90
Size: 40(0x28) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ff88b12d6c8 4001843 8 System.__Canon[] 0 instance 000001f6b14ae380 _items
00007ff88b123e98 4001844 18 System.Int32 1 instance 3506 _size
00007ff88b123e98 4001845 1c System.Int32 1 instance 3506 _version
00007ff88b121c98 4001846 10 System.Object 0 instance 0000000000000000 _syncRoot
00007ff88b12d6c8 4001847 8 System.__Canon[] 0 static <no information>
从输出可以清晰的看到,当前的 list.count=3506 个,在加上有 38 个线程在并发处理,所以最坏情况下是 3506 * 38 = 13w 的解密操作,难怪说高峰期的时候 CPU 爆高。
解决办法也很简单,把 xxx.DesDecrypt 给提出来,将解密操作由原来的 13w 优化到 38 次,下午大妹子反馈问题已经解决。

看到大妹子开心的笑了,我的饭有着落了
三:总结
这个问题是一个不良习惯的写法造成的,说实话,我不是看到线程栈上的 WhereListIterator 方法我也不相信会执行多次,可能最近中了 SQLSERVER 的毒,不管怎么说,这些都是小事,有大餐吃才是最重要的!
记一次 .NET 某医保平台 CPU 爆高分析的更多相关文章
- 记一次 .NET 某娱乐聊天流平台 CPU 爆高分析
一:背景 1.讲故事 前段时间有位朋友加微信,说他的程序直接 CPU=100%,每次只能手工介入重启,让我帮忙看下到底怎么回事,哈哈,这种CPU打满的事故,程序员压力会非常大, 我让朋友在 CPU 高 ...
- 记一次 .NET游戏站程序的 CPU 爆高分析
一:背景 1. 讲故事 上个月有个老朋友找到我,说他的站点晚高峰 CPU 会突然爆高,发了两份 dump 文件过来,如下图: 又是经典的 CPU 爆高问题,到目前为止,对这种我还是有一些经验可循的. ...
- 记一次 .NET 某医院HIS系统 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友加 wx 抱怨他的程序在高峰期总是莫名其妙的cpu爆高,求助如何分析? 和这位朋友沟通下来,据说这问题困扰了他们几年,还请了微软的工程师过来解决,无疾而终,应该还 ...
- 记一次 .NET 某旅行社Web站 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序内存经常飙升,cpu 偶尔飙升,没找到原因,希望帮忙看一下. 可惜发过来的 dump 只有区区2G,能在这里面找到内存泄漏那真有两把刷子..., ...
- 记一次 .NET 某电商交易平台Web站 CPU爆高分析
一:背景 1. 讲故事 已经连续写了几篇关于内存暴涨的真实案例,有点麻木了,这篇换个口味,分享一个 CPU爆高 的案例,前段时间有位朋友在 wx 上找到我,说他的一个老项目经常收到 CPU > ...
- 记一次 .NET 某机械臂智能机器人控制系统MRS CPU爆高分析
一:背景 1. 讲故事 这是6月中旬一位朋友加wx求助dump的故事,他的程序 cpu爆高UI卡死,问如何解决,截图如下: 在拿到这个dump后,我发现这是一个关于机械臂的MRS程序,哈哈,在机械臂这 ...
- 记一次 .NET 车联网云端服务 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序CPU经常飙满,没找到原因,希望帮忙看一下. 这些天连续接到几个cpu爆高的dump,都看烦了,希望后面再来几个其他方面的dump,从沟通上看, ...
- 记一次 .NET 某智慧物流 WCS系统 CPU 爆高分析
一:背景 1. 讲故事 哈哈,再次见到物流类软件,上个月有位朋友找到我,说他的程序出现了 CPU 爆高,让我帮忙看下什么原因,由于那段时间在苦心研究 C++,分析和经验分享也就懈怠了,今天就给大家安排 ...
- 记一次 .NET 某安全生产信息系统 CPU爆高分析
一:背景 1.讲故事 今天是的第四天,头终于不巨疼了,写文章已经没什么问题,赶紧爬起来写. 这个月初有位朋友找到我,说他的程序出现了CPU爆高,让我帮忙看下怎么回事,简单分析了下有两点比较有意思. 这 ...
- 记一次 .NET 某资讯论坛 CPU爆高分析
大概有11天没发文了,真的不是因为懒,本想前几天抽空写,不知道为啥最近求助的朋友比较多,一天都能拿到2-3个求助dump,晚上回来就是一顿分析,有点意思的是大多朋友自己都分析了几遍或者公司多年的牛皮藓 ...
随机推荐
- i春秋phone number
点开题目是一个普普通通的登录注册界面,随便注册一个点进去有两个功能,一个是查看电话和你相同的用户,一个是登出. 点击查询就可以看到用户数 这里有访问数据库的操作应该,所以就应该用数据库注入来解题. 又 ...
- Nginx 安装篇-yum安装
yum安装教程引用: https://www.cnblogs.com/AprilBlank/p/11388990.html#1-yum安装推荐 避坑事项: 暂无
- Day29 Linux相关命令的使用
今日内容 基本概念 安装 基本命令 在linux上安装软件 jdk mysql jdk Nginx的安装 一.概述 1.Unix linux基于Unix,Unix由贝尔实验室在1969年开发 一开始由 ...
- 保存sklearn中模型的两种方法(pickle、joblib)
保存sklearn中模型的两种方法(pickle.joblib) from sklearn import svm from sklearn import datasets clf = svm.SVC( ...
- python3 利用当前时间、随机数产生一个唯一的数字作为文件名
一.python3 利用当前时间.随机数产生一个唯一的数字作为文件名 代码如下: #-*-coding:utf-8-*- #python3自动生成文件名 from datetime import * ...
- 秒懂 Golang 中的 条件变量(sync.Cond)
本篇文章面向的读者: 已经基本掌握Go中的 协程(goroutine),通道(channel),互斥锁(sync.Mutex),读写锁(sync.RWMutex) 这些知识.如果对这些还不太懂,可以先 ...
- uniapp vue3下的代理转发不生效问题,亲测有效解决
以前配置过vue vite 的代理转发,没想到在uniapp的代理转发下翻车了,其实是一个很小的问题.调试过程中,尝试了webpack.vite 等写法 在根目录下 创建了 vite.config.j ...
- python语法之注释
引言 注释的最大作用是提高程序的可读性,在开发过程中非常有必要加上注释.Python 支持两种类型的注释,分别是单行注释和多行注释. 1 单行注释 Python 使用井号#作为单行注释的符号,语法格式 ...
- 丧心病狂,竟有Thread.sleep(0)这种神仙写法?
前言 最近在网上看到了一段代码,让我感到很迷茫.他在代码中使用了 Thread.sleep(0),让线程休眠时间为0秒,具体代码如下. int i = 0; while (i<10000000) ...
- CH392/CH395常见问题解决方法指南
CH395 问题 1: CH395 初始化失败.解答: 1.首先检查"check_exist"命令,正常情况下 CH395 会将该命令的输入值按位取反后输出,若该命令不正常,则说明 ...