记一次 .NET 某医保平台 CPU 爆高分析

一次,net,医保,平台,cpu,分析 · 浏览次数 : 2493

小编点评

1. 解密操作真的会爆高吗就算WHERE中逐项处理解密操作就一定会爆高吗?这是一个需要求证的问题,可以切到某个线程上用 !dso 找出那个 list,然后 !do 即可,参考代码如下:0:234> !DumpObj /d 000001f631799240Name: System.Collections.Generic.List`1[[xxxx_UserInfo,xxxx]]MethodTable: 00007ff82f5f3b20EEClass: 00007ff88ab59f90Size: 40(0x28) bytesFile: C:\\Windows\\Microsoft.Net\\assembly\\GAC_64\\mscorlib\\v4.0_4.0.0.0__b77a5c561934e089\\mscorlib.dllFields: MT Field Offset Type VT Attr Value Name00007ff88b12d6c8 4001843 8 System.__Canon[] 0 instance 000001f6b14ae380 _items00007ff88b123e98 4001844 18 System.Int32 1 instance 3506 _size00007ff88b123e98 4001845 1c System.Int32 1 instance 3506 _version00007ff88b121c98 4001846 10 System.Object 0 instance 0000000000000000 _syncRoot00007ff88b12d6c8 4001847 8 System.__Canon[] 0 static <no information>从输出可以清晰的看到,当前的 list.count=3506 个,在加上有 38 个线程在并发处理,所以最坏情况下是 3506 * 38 = 13w 的解密操作,难怪说高峰期的时候 CPU 爆高。解决办法也很简单,把 xxx.DesDecrypt 给提出来,将解密操作由原来的 13w 优化到 38 次,下午大妹子反馈问题已经解决。 2. 解密操作真的会爆高吗就算WHERE中逐项处理解密操作就一定会爆高吗?这是一个需要求证的问题,可以切到某个线程上用 !dso 找出那个 list,然后 !do 即可,参考代码如下:0:234> !DumpObj /d 000001f631799240Name: System.Collections.Generic.List`1[[xxxx_UserInfo,xxxx]]MethodTable: 00007ff82f5f3b20EEClass: 00007ff88ab59f90Size: 40(0x28) bytesFile: C:\\Windows\\Microsoft.Net\\assembly\\GAC_64\\mscorlib\\v4.0_4.0.0.0__b77a5c561934e089\\mscorlib.dllFields: MT Field Offset Type VT Attr Value Name00007ff88b12d6c8 4001843 8 System.__Canon[] 0 instance 000001f6b14ae380 _items00007ff88b123e98 4001844 18 System.Int32 1 instance 3506 _size00007ff88b123e98 4001845 1c System.Int32 1 instance 3506 _version00007ff88b121c98 4001846 10 System.Object 0 instance 0000000000000000 _syncRoot00007ff88b12d6c8 4001847 8 System.__Canon[] 0 static <no information>从输出可以清晰的看到,当前的 list.count=3506 个,在加上有 38 个线程在并发处理,所以最坏情况下是 3506 * 38 = 13w 的解密操作,难怪说高峰期的时候 CPU 爆高。解决办法也很简单,把 xxx.DesDecrypt 给提出来,将解密操作由原来的 13w 优化到 38 次,下午大妹子反馈问题已经解决。 3. 解密操作真的会爆高吗就算WHERE中逐项处理解密操作就一定会爆高吗?这是一个需要求证的问题,可以切到某个线程上用 !dso 找出那个 list, then !do 即可,参考代码如下:0:234> !DumpObj /d 000001f631799240Name: System.Collections.Generic.List`1[[xxxx_UserInfo,xxxx]]MethodTable: 00007ff82f5f3b20EEClass: 00007ff88ab59f90Size: 40(0x28) bytesFile: C:\\Windows\\Microsoft.Net\\assembly\\GAC_64\\mscorlib\\v4.0_4.0.0.0__b77a5c561934e089\\mscorlib.dllFields: MT Field Offset Type VT Attr Value Name00007ff88b12d6c8 4001843 8 System.__Canon[] 0 instance 000001f6b14ae380 _items00007ff88b123e98 4001844 18 System.Int32 1 instance 3506 _size00007ff88b123e98 4001845 1c System.Int32 1 instance 3506 _version00007ff88b121c98 4001846 10 System.Object 0 instance 0000000000000000 _syncRoot00007ff88b12d6c8 4001847 8 System.__Canon[] 0 static <no information>从输出可以清晰的看到,当前的 list.count=3506 个,在加上有 38 个线程在并发处理,所以最坏情况下是 3506 * 38 = 13w 的解密操作,难怪说高峰期的时候 CPU 爆高。解决办法也很简单,把 xxx.DesDecrypt 给提出来,将解密操作由原来的 13w 优化到 38 次,下午大妹子反馈问题已经解决。

正文

一:背景

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();
        }

但看了线程栈上的 WhereListIteratorFirstOrDefault 方法,貌似没有做优化,为了验证我的想法,我还特意写了段代码。


    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. 讲故事 一直在追这个系列的朋友应该能感受到,我给这个行业中无数的陌生人分析过各种dump,终于在上周有位老同学找到我,还是个大妹子,必须有求必应 😁😁😁。 妹子公司的系统最近在某次升级之后,在高峰期会遇到 CPU 爆高的现象,有些单位你懂的,很强势,所以就苦逼了程序媛,不管怎么

记一次 .NET 某医院预约平台 非托管泄露分析

## 一:背景 ### 1. 讲故事 前几天有位朋友找到我,说他的程序有内存泄露,让我帮忙排查一下,截图如下: ![](https://img2023.cnblogs.com/blog/214741/202307/214741-20230705122211449-1056329846.png) 说实

记一次 .NET 某医疗住院系统 崩溃分析

一:背景 1. 讲故事 最近收到了两起程序崩溃的dump,查了下都是经典的 double free 造成的,蛮有意思,这里就抽一篇出来分享一下经验供后面的学习者避坑吧。 二:WinDbg 分析 1. 崩溃点在哪里 windbg 带了一个自动化分析命令 !analyze -v 可以帮助我们找到崩溃时的

记一次 .NET 某医院门诊软件 卡死分析

一:背景 1. 讲故事 前几天有位朋友找到我,说他们的软件在客户那边卡死了,让我帮忙看下是怎么回事?我就让朋友在程序卡死的时候通过 任务管理器 抓一个 dump 下来,虽然默认抓的是 wow64 ,不过用 soswow64.dll 转还是可以的,参考命令如下: .load C:\soft\soswo

记一次 .NET某上位视觉程序 离奇崩溃分析

一:背景 1. 讲故事 前段时间有位朋友找到我,说他们有一个崩溃的dump让我帮忙看下怎么回事,确实有太多的人在网上找各种故障分析最后联系到了我,还好我一直都是免费分析,不收取任何费用,造福社区。 话不多说,既然有 dump 来了,那就上 windbg 说话吧。 二:WinDbg 分析 1. 为什么

记一次 .NET某酒业业务系统 崩溃分析

一:背景 1. 讲故事 前些天有位朋友找到我,说他的程序每次关闭时就会自动崩溃,一直找不到原因让我帮忙看一下怎么回事,这位朋友应该是第二次找我了,分析了下 dump 还是挺经典的,拿出来给大家分享一下吧。 二:WinDbg 分析 1. 为什么会崩溃 找崩溃原因比较简单,用 !analyze -v 命

记一次 .NET某网络边缘计算系统 卡死分析

一:背景 1. 讲故事 早就听说过有什么 网络边缘计算,这次还真给遇到了,有点意思,问了下 chatgpt 这是干嘛的 ? 网络边缘计算是一种计算模型,它将计算能力和数据存储位置从传统的集中式数据中心向网络边缘的用户设备、传感器和其他物联网设备移动。这种模型的目的是在接近数据生成源头的地方提供更快速

记一次 .NET某机械臂上位系统 卡死分析

一:背景 1. 讲故事 前些天有位朋友找到我,说他们的程序会偶发性的卡死一段时间,然后又好了,让我帮忙看下怎么回事?窗体类的程序解决起来相对来说比较简单,让朋友用procdump自动抓一个卡死时的dump,拿到dump之后,上 windbg 说话。 二:WinDbg 分析 1. 主线程在做什么 要想

记一次 .NET某工厂报警监控设置 崩溃分析

一:背景 1. 讲故事 前些天有位朋友在微信上丢了一个崩溃的dump给我,让我帮忙看下为什么出现了崩溃,在 Windows 的事件查看器上显示的是经典的 访问违例 ,即 c0000005 错误码,不管怎么说有dump就可以上windbg开干了。 二:WinDbg 分析 1. 程序为谁崩溃了 在 Wi

记一次 .NET某游戏币自助机后端 内存暴涨分析

一:背景 1. 讲故事 前些天有位朋友找到我,说他们的程序内存会偶发性暴涨,自己分析了下是非托管内存问题,让我帮忙看下怎么回事?哈哈,看到这个dump我还是非常有兴趣的,居然还有这种游戏币自助机类型的程序,下次去大玩家看看他们出币的机器后端是不是C#写的?由于dump是linux上的程序,刚好win