记一次某制造业ERP系统 CPU打爆事故分析

一:背景

1.讲故事

前些天有位朋友微信找到我,说他的程序出现了CPU阶段性爆高,过了一会就下去了,咨询下这个爆高阶段程序内部到底发生了什么?画个图大概是下面这样,你懂的。

30e560de84427a607dca00bc25df9185.png

按经验来说,这种情况一般是程序在做 CPU 密集型运算,所以让朋友在 CPU 高的时候间隔 5~10s 抓两个 dump 下来,然后就是用 WinDbg 分析。

二:WinDbg 分析

1. CPU 真的爆高吗

耳听为虚,眼见为实,我们用 !tp 观察下当前的CPU情况。

0:000> !tp
CPU utilization: 100%
Worker Thread: Total: 16 Running: 2 Idle: 14 MaxLimit: 32767 MinLimit: 2
Work Request in Queue: 0
--------------------------------------
Number of Timers: 2
--------------------------------------
Completion Port Thread:Total: 2 Free: 2 MaxFree: 4 CurrentLimit: 2 MaxLimit: 1000 MinLimit: 2

果不其然,CPU直接打满,接下来就是看看当前有几个CPU逻辑核,这么不够扛。。。

0:000> !cpuid
CP  F/M/S  Manufacturer     MHz0  6,106,6  <unavailable>   27001  6,106,6  <unavailable>   2700

我去,一个生产环境居然只有两个核。。。果然这大环境下公司活着都不够滋润。

2. 到底是谁引发的

既然是阶段性爆高,最简单粗暴的就是看下各个线程栈,使用 ~*e !clrstack 命令即可,因为只有两核,所以理论上两个线程就可以把 CPU 干趴下,扫了一下线程栈,果然有对号入座的,输出信息如下:

0:000> ~*e !clrstack 
OS Thread Id: 0x146c (42)Child SP               IP Call Site
00000089abcfca18 00007ffc4baffdb4 [InlinedCallFrame: 00000089abcfca18] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089abcfca18 00007ffbdd4a7a48 [InlinedCallFrame: 00000089abcfca18] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089abcfc9f0 00007ffbdd4a7a48 DomainNeutralILStubClass.IL_STUB_PInvoke(System.Runtime.InteropServices.HandleRef)
00000089abcfcaa0 00007ffbdd52ad0a System.Drawing.SafeNativeMethods+Gdip.GdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089abcfcae0 00007ffbdd52ac3f System.Drawing.Image.Dispose(Boolean)
00000089abcfcb30 00007ffbdd556b5a System.Drawing.Image.Dispose()
00000089abcfcb60 00007ffbe39397c7 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089abcfcc00 00007ffbe3939654 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089abcfcd30 00007ffbe39382e1 NPOI.SS.Util.SheetUtil.GetColumnWidth(NPOI.SS.UserModel.ISheet, Int32, Boolean)
00000089abcfcdc0 00007ffbe39380bc NPOI.XSSF.UserModel.XSSFSheet.AutoSizeColumn(Int32, Boolean)
...OS Thread Id: 0x1c8c (46)Child SP               IP Call Site
00000089ad43dba8 00007ffc4baffdb4 [InlinedCallFrame: 00000089ad43dba8] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089ad43dba8 00007ffbdd4a7a48 [InlinedCallFrame: 00000089ad43dba8] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089ad43db80 00007ffbdd4a7a48 DomainNeutralILStubClass.IL_STUB_PInvoke(System.Runtime.InteropServices.HandleRef)
00000089ad43dc30 00007ffbdd52ad0a System.Drawing.SafeNativeMethods+Gdip.GdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089ad43dc70 00007ffbdd52ac3f System.Drawing.Image.Dispose(Boolean)
00000089ad43dcc0 00007ffbdd556b5a System.Drawing.Image.Dispose()
00000089ad43dcf0 00007ffbe39397c7 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089ad43dd90 00007ffbe3939654 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089ad43dec0 00007ffbe39382e1 NPOI.SS.Util.SheetUtil.GetColumnWidth(NPOI.SS.UserModel.ISheet, Int32, Boolean)
00000089ad43df50 00007ffbe39380bc NPOI.XSSF.UserModel.XSSFSheet.AutoSizeColumn(Int32, Boolean)
...
00000089ad43e460 00007ffbe115b193 System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(System.Web.Mvc.ControllerContext, System.Web.Mvc.ActionDescriptor, System.Collections.Generic.IDictionary`2<System.String,System.Object>)
...
00000089abcfd310 00007ffbe115b147 System.Web.Mvc.Async.AsyncControllerActionInvoker+c.b__9_0(System.IAsyncResult, ActionInvocation)
...

有些朋友要问了,你是怎么确定就是这两个线程呢?其实有两个方法可以验证。

  1. 使用 !whttp 看http请求

既然是 web 请求,自然就可以拿到里面的 HttpContext,这里面记录着当前请求的运行时间,这个信息非常重要,截图如下:

6ad0314921624c2bc95b7fd37b6ba865.png

从图中可以看到,有两个 xxxx/Export 请求运行时间非常高,一个是 4min30s ,一个是 50s ,刚好落在了 4246 号线程上。

  1. 借助第二个 dump 文件

这就是为什么要抓二个dump的原因了,因为另一个dump会给我们相当有价值的对比信息,同样使用 !whttp 验证。

c7e39d4754631094c49f6195ae21d761.png

接下来我们就要调研为什么这两个线程会运行这么久?

3. 为什么会运行这么久

既然是 Export 导出文件,第一时间就应该想到是不是和数据量有关?通过线程栈上的方法,发现是一个List 集合,接下来用 !dso 命令找出来看看。

0:042> !dso
OS Thread Id: 0x146c (42)
RSP/REG          Object           Name
00000089ABCFCAC8 0000020683b7c128 System.Drawing.Bitmap
00000089ABCFCAF8 0000020683b7c158 System.Drawing.Graphics
00000089ABCFCB10 0000020683b7c128 System.Drawing.Bitmap
00000089ABCFCB30 0000020683b7c128 System.Drawing.Bitmap
00000089ABCFCB40 0000020683b7c4d0 NPOI.XSSF.UserModel.XSSFCellStyle
00000089ABCFCB50 0000020683b7c198 NPOI.XSSF.UserModel.XSSFRichTextString
00000089ABCFCB68 0000020683b7c198 NPOI.XSSF.UserModel.XSSFRichTextString
00000089ABCFCBC0 0000020683b7c198 NPOI.XSSF.UserModel.XSSFRichTextString
00000089ABCFCBC8 0000020683b7c2e8 System.String[]
00000089ABCFCBD0 0000020683b7c360 System.Drawing.Font
00000089ABCFCDE8 0000020666501240 System.Collections.Generic.List`1[[System.Collections.Generic.List`1[[System.Object, mscorlib]], mscorlib]]
...0:042> !do 0000020666501240
Name:        System.Collections.Generic.List`1[[System.Collections.Generic.List`1[[System.Object, mscorlib]], mscorlib]]
MethodTable: 00007ffbde342440
EEClass:     00007ffc36fc2af8
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
00007ffc36e4e250  40018a0        8     System.__Canon[]  0 instance 00000207658592d8 _items
00007ffc36e385a0  40018a1       18         System.Int32  1 instance            44906 _size
00007ffc36e385a0  40018a2       1c         System.Int32  1 instance            44906 _version
00007ffc36e35dd8  40018a3       10        System.Object  0 instance 0000000000000000 _syncRoot
00007ffc36e4e250  40018a4        0     System.__Canon[]  0   shared           static _emptyArray>> Domain:Value dynamic statics NYI 0000020563eec3c0:NotInit dynamic statics NYI 0000020795f5b9a0:NotInit  <<

可以清楚的看到,这个list高达 4.5w,这个量级说多也不多,说少也不少,言外之意就是代码写的也不好不到哪里去。

4. 用户代码要承担责任吗

要判断用户代码是不是很烂,除了白盒看代码,也可以黑盒观察这几个线程栈,可以发现两个dump 显示的栈信息都和 AutoSizeColumn 方法有关。

00000089abcfcae0 00007ffbdd52ac3f System.Drawing.Image.Dispose(Boolean)
00000089abcfcb30 00007ffbdd556b5a System.Drawing.Image.Dispose()
00000089abcfcb60 00007ffbe39397c7 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089abcfcc00 00007ffbe3939654 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089abcfcd30 00007ffbe39382e1 NPOI.SS.Util.SheetUtil.GetColumnWidth(NPOI.SS.UserModel.ISheet, Int32, Boolean)
00000089abcfcdc0 00007ffbe39380bc NPOI.XSSF.UserModel.XSSFSheet.AutoSizeColumn(Int32, Boolean)

从名字看是 NOPI 提供的自动调整列宽 的方法,那是不是这个方法的单次性能很慢呢?要寻找答案,只能求助百度啦。。。

  • 图一dd61e06d0fb9c5867b28411ce0cf4d9b.png

  • 图二bd00ba112fa574b1c65661bca77f65d7.png

到这里我们基本就搞清楚了,导致 reqeust 高达 5min + 的诱因大概有三个。

  1. 数据量大

  2. AutoSizeColumn 速度慢

  3. 代码上的其他因素

跟朋友沟通后,朋友说这块请求中的 AutoSizeColumn 方法忘了改掉。

三:总结

这个 Dump 分析起来其实非常简单,思路也比较明朗,重点还是提醒一下大家慎用 NPOI 的 AutoSizeColumn 方法,弄不好就得出个生产事故!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/282044.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PC端和移动APP端CSS样式初始化

CSS样式初始化分为PC端和移动APP端 1.PC端&#xff1a;使用Normalize.css Normalize.css是一种CSS reset的替代方案。 我们创造normalize.css有下面这几个目的&#xff1a; 保护有用的浏览器默认样式而不是完全去掉它们一般化的样式&#xff1a;为大部分HTML元素提供修复浏览器…

框架实现修改功能的原理_JAVA集合框架的特点及实现原理简介

1.集合框架总体架构集合大致分为Set、List、Queue、Map四种体系,其中List,Set,Queue继承自Collection接口&#xff0c;Map为独立接口Set的实现类有:HashSet&#xff0c;LinkedHashSet&#xff0c;TreeSet...List下有ArrayList&#xff0c;Vector&#xff0c;LinkedList...Map下…

NPM报错终极大法

2019独角兽企业重金招聘Python工程师标准>>> 所有的错误基本上都跟node的版本相关 直接删除系统中的node 重新安装 sudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*} 重新安装 $ n lts $ npm install -g npm $ n stable…

10.python网络编程(socket server 实现并发 part 2)

一、基于tcp的socket通信的基本原理分析。基于tcp的socket通信&#xff0c;主要依靠两个循环&#xff0c;分别是连接循环和通信循环。这个前面的文章有写过&#xff0c;在这里就不再重复了。二、socketserver实现多并发的原理分析。1.server类&#xff1a;2.reques类。类继承关…

如何在一小时内更新100篇文章?-Evernote Sync插件介绍

上一篇“手把手教你制作微信小程序&#xff0c;开源、免费、快速搞定”&#xff0c;已经教会你如何快速制作一个小程序&#xff0c;但作为资讯类小程序&#xff0c;内容不可少&#xff0c;并且还需要及时更新。 但是&#xff0c;如果让你复制粘贴&#xff0c;可能还需要上传图片…

iOS无线真机调试

为什么80%的码农都做不了架构师&#xff1f;>>> Xcode从9开始 就支持无线真机调试&#xff0c;那么怎么操作呢&#xff1f; 首先用数据线连接你的设备&#xff0c;接下来Xcode- Window-Devices and Simulators 点开之后看到你的设备 默认情况下Connect via networ…

Mybatis中jdbcType和javaType的对应关系

2019独角兽企业重金招聘Python工程师标准>>> Mybatis中jdbcType和javaType的对应关系 1 JDBC Type Java Type 2 CHAR String 3 VARCHAR String 4 LONGVARCHAR String 5 NUMERIC java.math.…

Codeigniter 4.0-dev 版源码学习笔记之四——详细路由过程

前言 我个人觉得在当前 MVC 流行的架构下&#xff0c;要想去了解一个框架&#xff0c;或者是一个基于此架构下的应用程序&#xff0c;最好的入手方式就是先看路由&#xff0c;虽然路由不是 MVC 里的任何一个&#xff0c;但是知道了路由的来龙去脉就知道了整个框架或者是应用的结…

python装饰器执行顺序

2019独角兽企业重金招聘Python工程师标准>>> 1、单个装饰器执行 上来先看代码&#xff1a; import timedef deco(func):functools.wraps(func)def _wrapper():startTime time.time()print "start"func()print "end"endTime time.time()msecs …

99. Recover Binary Search Tree

一、题目 1、审题 2、分析 给出一个二叉查找树&#xff0c;其中有两个元素的位置弄错了&#xff0c;写算法将其恢复。 二、解答 1、思路&#xff1a; 方法一、 通过中序遍历可以确定一棵二叉查找树由小到大的顺序。 所以在此错位的查找树中查找到的节点中有 1 个比后续节点值大…

myeclipse+git pull项目报错

2019独角兽企业重金招聘Python工程师标准>>> 1.在本地工程目录(.git)找到config文件&#xff1b; 2.修改config文件内容为&#xff1a; [core] repositoryformatversion 0 filemode false logallrefupdates true [branch "master"] remote origin m…

如何关掉macbook的开机声音

1、系统偏好设置->声音 2、关掉“启动时播放声音” 这样设置之后&#xff0c;macbook再开机就没有“咚”的那个声音了

oatdata结构详解

段名称 文件内偏移段大小ELF头部0x000000000x00001000oatdata0x000010000x027b8000oatexec0x027b90000x01ed69ecELF尾部0x046900000x00001000OatHeader&#xff1a; 0x00001000 | 6F 61 74 0A 30 33 39 00 77 40 00 B1 03 00 00 00 | 0x00001010 | 01 00 00 00 19 00 00 00 00…

开源 java CMS - FreeCMS2.6 Web页面信息采集

2019独角兽企业重金招聘Python工程师标准>>> java开源论坛系统http://javabbs.javaz.cn 项目地址&#xff1a;http://www.freeteam.cn/ Web页面信息采集 从FreeCMS 2.1开始支持 通过简单配置即可抓取目标网页信息&#xff0c;支持增量式采集、关键字替换、定时采集&…

ORACLE关于段的HEADER_BLOCK的一点浅析

在学习段&#xff08;segment&#xff09;、区间&#xff08;extent&#xff09;时&#xff0c;对段的HEADER_BLOCK有一些疑问&#xff0c;本文记录一下探究的实验过程以及相关总结&#xff0c;&#xff0c;如有不对的地方&#xff0c;敬请指出。以SCOTT.EMP表为例&#xff08;…

【源码探索】.NET中的List,为什么即有Count属性又有Count()方法

“优秀的程序员的标准之一是&#xff1a;编写更易于扩展的代码”图片&#xff1a;奥森公园的向日葵 拍摄于2022年7月23日01—问题缘起上一篇中&#xff0c;我们知道List<T>的是基于数组实现的可变长度的列表。很多小伙伴发现&#xff0c;List<T>即有Count属性又有C…

使用ASP.NET广告控件的XML语言创建广告链接--ASP.NET

1、AdRotator广告控件的所有属性都是可选的&#xff0c;XML文件中可以包含如下表所示的属性&#xff08;XML文件的广告属性&#xff09;。 属性 说明 ImageUrl 要显示的图像的URL NavigateUrl 单击AdRotator控件时要转到的网页URL AlternateText 图像不可用时现实的问…

vim编辑和命令模式、实践

2019独角兽企业重金招聘Python工程师标准>>> 9月29日任务 5.5 进入编辑模式 5.6 vim命令模式 5.7 vim实践 Vim编辑模式 进入编辑模式 操作 说明 i 在光标所在字符前插入内容 I 在光标所在行行首插入内容 a 在光标所在字符后插入内容 A 在光标所在行行尾插入…

英语自动提取高频词_斑马英语提分营免费体验课

斑马英语电脑版是一款专业可靠的英语学习软件&#xff0c;斑马英语官方版可以帮助孩子学习纯正的英语口语发音&#xff0c;以讲故事的形式让孩子学习单词及口语练习&#xff0c;斑马英语电脑版针对儿童语言特征设计的智能口语测评系统&#xff0c;能够自动识别发音和评分&#…