最近发现服务器上某个web站点老是CPU很高,该站点部署在IIS上,我IIS上有多个站点,每个站点一个进程池,每个进程池取名都是根据站点来取的,所以很容易看出哪个站点吃掉的CPU,该站点已运行十几年,是基于.net 4.8 framework 编写的web站点(十几年的老项目重构的话就不用提,新项目大家还是用.net core加个前端框架,前后端分离 ,真的很香)
发现CPU很高的时候,我第一反应是用windbg来分析进程池的转储文件(即创建.dmp文件,windbg是加载.dmp文件来分析的),因为以前没做过这个工作,真的花了很长时间踩坑(2天,所以这篇文章是我踩坑2天总结的)
先说第一个坑,我用的服务器系统是windows server2019 64位的操作系统,我的web站的IIS进程池->高级设置中开启了“启用32位应用程序”(因为某个读写excel的组件是基于32位的),所以我的进程池是运行在32位模式下的,但是我一开始并不知道,我是直接在操作系统的任务管理器中找到这个进程池的进程,然后右键点击“创建转储文件”,但是各位千万要注意,这里有大坑,因为64位的操作系统的任务管理器这个是64位的(任务管理器的进程名是 Taskgmr.exe),,所以我是在用 64位的任务管理器,创建32位的进程池的转储文件,这里一定不能这么干,如果这么干,接下来你无论做何种努力,都将得到很多莫名其妙的反馈(windbg能加载,但是总有莫名其妙的问题,visual studio也能分析,也得不到正确的结果,我后面会说结果里面缺少了什么,总之你记住这个坑是我花了一天多时间才踩出来的。。。。。),那如何确认自己的进程池是64位还是32位呢,你可以打开服务器的任务管理器,然后点击“详细信息”这个标签,然后在任务管理器列表的标题上右键,会出现“隐藏列、选择列”,你点击“选择列”,然后拖动滚动条,往下拉找到“平台”勾选上(图1),任务管理器上就会多出一列平台,上面会显示“64位或32位”,如下图2
图1
图2
从上图2可以发现,我PID=11720这个 w3wp.exe 它是32位的,我的任务管理器却是64位的,这样子创建出来的转储文件就有问题,正确创建转储文件的是用32位的任务管理器去创建32位进程的转储文件,64位系统里面其实自带了32位的任务管理器的,路径如下:C:WindowsSysWOW64Taskmgr.exe (64位的任务管理器的路径是:C:WindowsSystem32Taskmgr.exe) ,打开32位任务管理器之前,要先关闭之前已经打开的64位的管理器,打开32位管理器后也一样去勾选“平台”这个列(确保自己没有开错任务管理器),可以从下图3看出来,我PID=9056 这个进程池显示是32位,taskmgr.exe也是显示32位(和上图的进程池没对上是因为我随便选了一个截图而已,这只是为了证明任务管理器和进程池的位数一致了)
图3
创建进程池的转储.dmp文件的这个操作一定要注意!!!:使用和自己进程池位数一致的任务管理器来创建转储文件.dmp非常关键(32位的进程池用32位任务管理器创建转储文件,64位进程池用64位任务管理器创建转储文件,如何打开不同任务管理器上面有介绍)!!我大多数时间都浪费在了因为创建错了dmp文件导致分析结果一直没正确(windbg可以加载,visual studio 也可以加载(visual studio可以分析.dmp文件,而且非常好用!),两个工具都可以进行分析,就是没办法给出准确结果!(这是非常要命的,因为我根本没怀疑过这个.dmp文件会有问题),比如visual studio 死活不给出“线程CPU使用率摘要”这个关键数据,其他数据给了,让我误以为我的.dmp文件没有问题,所以掉进这个坑里面掉了这么久。。。。如果它干脆加载不了,我也会怀疑到是我的.dmp文件有问题)
你要在你CPU高的时候去用任务管理器手动创建转储文件,你也可以使用其他工具,在CPU超过某个阈值的时候自动创建.dmp文件(自己搜一下,我没有用工具不好介绍经验)
如果你已经按上面这个操作创建了.dmp文件,想快速知道CPU高在哪里的原因(毕竟现在你线上环境正出问题,哪有时间去想windbg怎么用,你现在关心的是问题出在哪,如果你是这样子想的,请拉到文章后面一点的位置,直接跳过windbg的使用问题,接下来的篇幅是说windbg的问题了,如果你只想先快速先解决问题,直接看文章结尾的visual studio分析.dmp文件的介绍)
==================================分割线=================使用WinDbg分析.dmp文件=============================================================================================
以下是使用windbg来分析问题(如果你现在火烧眉毛了就别去看这个段落,往下拉到是用visual studio 分析.dmp文件去看)
因为写这篇博文的时候,真的非常忙,我不写博文又怕自己忘记了这个经过,下次自己又要踩坑,所以简单的写一下,用windbg也踩了不少坑,为了节省时间(现在真的很忙),简单来说一下:
1、确保你用的windbg的版本足够新(因为新的版本会自动从微软的符号服务器下载和这个dmp文件匹配的相关dll) ,我一共安装了3个版本的windbg,从老版本到新版本分别是:6.12.0002(最老)、10.0.26100.1、最新版:1.2407.24003.0(Debugger client version),10.0.27668.1000(Debugger engine version)
我发现好像老版本的分 64位和32位的windbg,但是最新版的,我看安装目录的文件,是两者兼容的,因为有人说得用32位的windbg分析32位的.dmp文件,64位的分析64位的.dmp文件,实际上我用最新版的,是可以加载64位的.dmp文件并且进行分析的,我用最新版的windbg尝试加载了32位的.dmp文件后,我从任务管理器发现它 Debug Engine Host Process这个进程显示的是32位,然后任务管理器上转到文件位置是 C:Program FilesWindowsAppsMicrosoft.WinDbg_1.2407.24003.0_x64__8wekyb3d8bbwex86 这个文件夹下的exe ,看它调用的是x86文件夹下的exe文件,说明它应该是正确识别了.dmp文件类型,然后使用了32位的exe打开了.dmp文件,然后我又尝试用它加载了一个64位的.dmp文件,发现任务管理器里面多了一个 Debug Engine Host Process ,没有显示32位,并且任务管理器上显示它加载的是文件路径是是 C:Program FilesWindowsAppsMicrosoft.WinDbg_1.2407.24003.0_x64__8wekyb3d8bbweamd64 ,明显是根据.dmp文件加载了不同类型的分析工具了。任务管理器截图如下:
结论:你别纠结32位还是64位的windbg,安装最新的版本,它会自动识别.dmp文件
附:最新版windbg安装教程,首先微软的地址 https://learn.microsoft.com/zh-cn/windows-hardware/drivers/debugger/ ,点击“下载WinDbg”,然后会下载到一个“windbg.appinstaller” ,双击这个文件,如果你能一切顺利安装完成,那就可以了,如果你安装不了提示“无法打开次应用包”的话,则用记事本打开 “windbg.appinstaller” ,里面有一个 Uri="https://windbg.download.prss.microsoft.com/dbazure/prod/1-2407-24003-0/windbg.msixbundle" ,复制这个Uri进行下载安装,我就是这么安装上的,不知道你可行不可行。
最新版的截图如下:
经过以上,你应该已经安装好了最新版的WinDbg了(不要再用旧版的WinDbg了,珍爱生命吧,我折腾了3个版本,最新版的是最好用的(能自动下载分析你dmp文件所需的东西、4k显示器分辨率显示适配的,老版本不适配、能自动识别.dmp文件是32位还是64位,自动加载正确的引擎分析你的.dmp文件,这三个理由足够你用最新版了))
安装好了以后,windows左下角搜索“windbg”,然后右键管理员权限运行(这是我的习惯,这种工具我喜欢管理员权限运行,怕权限问题各种未知错误,实际上是否需要管理员权限运行我不确定),再点击“文件->open Source file”,然后选择你刚刚的那个.dmp文件。
正常来说,应该会自动下载相关需要的.dll文件了,如果没有的话,你可以执行一下:.reload /f (这个过程很久,有时候都得十几分钟,如果你有梯子挂个梯子会快很多)
经过以上步骤,按道理现在该需要的Dll你应该就有了,可以执行一下命令: .cordll -ve -u -l (这个命令可以让WinDbg重新卸载和加载一下核心dll,并显示加载的情况。微软WinDbg使用文档如下: https://learn.microsoft.com/zh-cn/windows-hardware/drivers/debugger/getting-started-with-windbg ,相关元命令微软说明地址:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/debuggercmds/meta-commands)
执行 .cordll -ve -u -l 这个命令结果如下图:
0:000> .cordll -ve -u -l CLRDLL: C:WindowsMicrosoft.NETFrameworkv4.0.30319mscordacwks.dll:4.8.9261.00 f:8 doesn't match desired version 4.8.4739.00 f:8 CLRDLL: Loaded DLL C:ProgramDataDbgsymmscordacwks_x86_x86_4.8.4739.00.dll665A9716849000mscordacwks_x86_x86_4.8.4739.00.dll Automatically loaded SOS Extension CLR DLL status: Loaded DLL C:ProgramDataDbgsymmscordacwks_x86_x86_4.8.4739.00.dll665A9716849000mscordacwks_x86_x86_4.8.4739.00.dll
从图中可以看出来,一开始WinDbg是直接从 C:WindowsMicrosoft.NETFrameworkv4.0.30319mscordacwks.dll 这个地址加载 mscordacwks.dll(下称本机dll)的 ,但是版本不对,因为本机dll的版本是4.8.9261(可以找到这个dll,右键属性看详细信息),然后WinDbg又开始从 C:ProgramDataDbgsymmscordacwks_x86_x86_4.8.4739.00.dll665A9716849000mscordacwks_x86_x86_4.8.4739.00.dll 这个路径加载,最后加载成功了。这个文件就是最新版的WinDbg自动从微软的符号服务器下载的。
DLL版本不对的原因:我本地开发机安装的.net framework是 4.8.9261 ,而我在服务器创建的.dmp文件当时创建的时候它的进程的运行环境是4.8.4739.00 ,所以会不匹配。就是因为:服务器上是一个版本,本地用于分析的是另外一个版本的.net版本。当初这个版本不对,我用老的WinDbg从网上各种搜文章,说是从服务器上复制下来放到本地,然后用 .cordll -lp D:你的DLL路径 ,各种弄,死活不能正确加载,这个坑也是掉进去很久,下载了最新版后这种问题都不是问题了,所以一直强调用最新版,老版的需要研究太多命令,搞错了容易得出错误结论(主要还是因为我不熟这个,熟的话老版本用起来估计也顺手的,但是我相信你此刻能看到这里,估计也是WinDbg的新手,和我一样老老实实用最新版的,节省不少折腾环境的时间)
服务器上具体用的什么版本,可以在WinDbg里面执行!analyze -v 命令,等它执行完毕后有一个:CLR.Version 就是服务器上的版本号。命令执行完 后部分结果如下(结果内容太多,我只复制了关键的一个,你在你的WinDbg里面Ctrl+F搜一下这个"CLR.Version"这个字符串可以看到)
0:000> !analyze -v
******************************************************************************* * * * Exception Analysis * * * ******************************************************************************* KEY_VALUES_STRING: 1
Key : CLR.Version Value: 4.8.4739.0
重点:当初我用了6.12版本的老版本windbg,然后从网上搜了好多介绍文章,然后根据文章从服务器复制sos.dll clr.dll mscordacwks.dll来我本地,然后学着网上说的指定这三个Dll的path(我觉得理论这样子没错),
实际上折腾了是真的久,死活都不行,提示类似下面这样子
0:000> .cordll -I clr -lp E:IISW3WPcore86server
CLR DLL status: No load attempts
但是用了最新版的,都是自动根据.dmp文件下载需要的dll到特定的目录,然后加载它自己下载的这个dll,也不需要自己去指定目录,这才像微软出品的工具,傻瓜式操作,我当初是查遍了各种文章,根据文章里面说的各种命令去尝试,从没有加载成功过,所以,珍爱生命,
用最新版的WinDbg!用最新版的WinDbg!用最新版的WinDbg!用最新版的WinDbg
执行了 .cordll -ve -u -l 命令,确保你看到的结果和我上面写的那样子的提示,这个时候应该就可以执行相关命令了
第一个命令:!runaway (该命令微软官方解释:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/debuggercmds/-runaway ),不出意外将会得到如下结果:
然后可以看到列出了很多线程,比如我选中44号线程,则可以用命令 ~44s 进入到这个线程 (输入完命令按 Enter,下面就会变为0:044),然后再执行 !clrstack 就可以看到更详细的信息了,可以看出来
也可以用命令列出所有的线程堆信息,命令是:~*e !clrstack
然后会列出一堆的信息,从信息里面大致就可以看出来可能是哪个页面出现问题。比如上面的我就可以看出来是 ordersend_orderoutlistfishy_aspx页面出问题,但是具体什么问题还得具体分析。
但是,我发现Visual studio 分析这个.dmp文件那是相当优秀,结果里面直接给我标出来哪一行,我直接看这行代码就怀疑上了问题,不想折腾WinDbg的强烈建议直接用Visual Studio分析试试,因为这个页面显示的内容非常非常非常多,然后页面上有个控件 label,然后用label.Text+=xxxx,拼了非常非常非常多的内容,所以我直接怀疑上了这里,然后改为StringBuilder,减少了四分之三的时间(从技术上解决,从2分钟减少到30秒),然后后来又从业务上继续去沟通了一下使用这个页面的同事,直接把功能改掉了(总结起来就是历史遗留问题,这个页面的这个数据压根没人看,要看这个页面的人,压根不需要这么多数据,只显示极少一部分的数据就可以了)
但是我现在是用WinDbg有的问题如下,需要WinDbg的大佬帮我解答一下:
1、如何像Visual Studio那样显示具体哪一行代码呢?我已经把我的webform根目录下所有的pdb文件复制到我本地,然后用 E:IISW3WPpdb ,然后用 .sympath+ E:IISW3WPpdb 将这个目录添加到我的符号目录,并且用
.reload /f 重新强制加载了。
2、我用 !runaway 命令显示出线程,然后这个24号线程是时间最长的,然后我用命令 ~24s 进入该线程,然后用 !clrstack命令想看相关信息,结果发现报错,内容如下面的,,为什么获取不到呢?请看到这个文章,知道问题在哪,或者有什么建议的麻烦给我留言,我想学一下这个WinDbg,我已经搜来搜去没有找到解决办法了。chatgpt也没回答对。按我的理解Visual studio可以显示,WinDbg肯定也可以,我怀疑是我的符号设置问题+Source Link问题,到底该如何设置,才能和Visual studio 这样子明确指出哪个行代码问题?我用WinDbg只找到了哪个页面问题,想继续找问题我还得去仔仔细细看这个页面代码,打日志才能知道。
0:024> !clrstack
OS Thread Id: 0x6d40 (24)
Child SP IP Call Site
GetFrameContext failed: 1
00000000 00000000
==================================分割线=================使用Visual Studio分析.dmp文件===========================================================================================
以下是使用visual studio 分析.dmp文件的经过,如果你现在火烧眉毛,先别去研究windbg,直接打开visual studio 工具吧!
我不知道你的问题,能不能直接用visual studio分析.dmp文件找到问题(我相信是可以的),反正我的是直接用visual studio找到了问题,非常直观,也不要倒腾windbg一堆的命令和环境,真的是宇宙第一编辑器(别扛这个观点,杠就是你对了),直接打开visual studio(我用的是visual studio2022的版本),打开visual studio 然后点击“文件->打开文件”,选择刚刚这个.dmp文件,如图4,点击右侧操作标签下的“运行诊断分析” 这个按钮,
点击“运行诊断分析后”如图5 ,全部勾选上那几个选项,点击“分析”按钮,会出现一个等待的对话框,等它执行完毕,就可以看到结果了,如图6
图6
然后鼠标点击这个“线程CPU使用率摘要”,右侧滚动条往下滚动一点,就可以看到具体哪些线程有问题,如图7
坑点:如果你分析的结果里面没有“线程CPU使用率摘要”这个分析结果,并且你确定这个.dmp文件一定吃了你很多CPU,那么你很有可能是.dmp文件的问题,因为我用64位管理器创建32位进程池创建出来的转储文件.dmp,visual studio 死活分析不出来这个“线程CPU使用率摘要”
然后根据上面的代码行数仔细看了一下这个页面的代码,好家伙,这是一个我们web站点处理有问题订单的页面,当初开发的时候(十几年前)因为客服的工作模式就是在这个页面看所有发货超时的订单(那个时候订单量少),然后通知发货员去及时发货,现在因为超时的订单太多太多了(每年高峰期都这个样),这个页面需要一口气展示非常非常多的内容,这些内容都是用一个 label.Text 控件输出的,然后写代码的时候拼接内容是 label.Text+= "xxxxx" ,所以这个页面是个完美的计算密集型线程,随便几个发货员访问下,整个服务器的CPU直接拉到100%。假设这个页面仅仅只是多访问几次数据库,或者查询数据比较久这种IO型的计算,那迟迟没响应,也不至于杀伤力这么大(顶多就是访问这个页面的人多等一下),关键在于它丫的瓶颈是拼接字符串。。。。。
后续优化就简单了,问了一下客服,根据客服目前工作的逻辑,直接不展示全部超时的(客服不关注超时的了,每个发货员自己关注自己的,发货员自己问题订单不多,然后label.Text+=这个写法也很二逼,改为了StringBuilder对象来拼接了。
至此,问题解决,这个问题耗费了我差不多两天的时间,最大的坑就是前面说的创建.dmp文件的问题,如果一开始创建对了.dmp文件,然后直接用Visual Studio来分析,那估计发现问题的时间只要1小时了。不过,程序员在解决问题的时候,踩坑也是一种学习,踩的坑够多,积累的经验越多,下次遇到问题解决的越快,对于踩这个坑,我一点也不懊恼。