记一次某制造业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元素提供修复浏览器…

FPGA浮点数定点化

因为在普通的fpga芯片里面&#xff0c;寄存器只可以表示无符号型&#xff0c;不可以表示小数&#xff0c;所以在计算比较精确的数值时&#xff0c;就需要做一些处理&#xff0c;不过在altera在Arria 10 中增加了硬核浮点DSP模块&#xff0c;这样更加适合硬件加速和做一些比较精…

框架实现修改功能的原理_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…

自己使用的一个.NET轻量开发结构

三个文件夹&#xff0c;第一个是放置前端部分&#xff0c;第二个是各种支持的类文件&#xff0c;第三个是单元测试文件。Core文件类库放置的是与数据库做交互的文件&#xff0c;以及一些第三方类库&#xff0c;还有与数据库连接的文件1.Lasy.Validator是一个基于Attribute验证器…

英语影视台词---八、the shawshank redemption

英语影视台词---八、the shawshank redemption 一、总结 一句话总结&#xff1a;肖申克的救赎 1、Its funny. On the outside, I was an honest man. Straight as an arrow. I had to come to prison to be a crook.&#xff1f; 这很有趣。 在外面&#xff0c;我是一个诚实的人…

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;可能还需要上传图片…

linux awk

grep 文本过滤器sed 流编辑器awk 报告生成器 格式化以后显示awk [option] PATTERN {action} file1 file2awk -F"|" BEGIN{OFS":"} {print $1,$2,$3} test.txt #文本字符串用双引号awk -F"|" BEGIN{OFS":"} {print $1,"jksong&quo…

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.…

java贪吃蛇

使用双向链表实现贪吃蛇程序 1.链表节点定义&#xff1a; package snake;public class SnakeNode {private int x;private int y;private SnakeNode next;private SnakeNode ahead;public SnakeNode() {}public SnakeNode(int x, int y) {super();this.x x;this.y y;}public …

【死磕 Spring】----- IOC 之解析 bean 标签:解析自定义标签

前面四篇文章都是分析 Bean 默认标签的解析过程&#xff0c;包括基本属性、六个子元素&#xff08;meta、lookup-method、replaced-method、constructor-arg、property、qualifier&#xff09;&#xff0c;涉及内容较多&#xff0c;拆分成了四篇文章&#xff0c;导致我们已经忘…

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

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

固态硬盘和机械硬盘的比较和SQLSERVER在两种硬盘上的性能差异

听说固态硬盘是高富帅的必备神器&#xff0c;本人为了提升工作效率和提高工作速度 这个月节衣缩食&#xff0c;终于也决定买了一块三星固态硬盘120G容量 这个固态硬盘拿在手里轻飘飘的&#xff0c; 好像里面什么东西都没有似的 废话少说&#xff0c;先上图 开机速度20秒左右 测…

大文件读写效率比较

之前做到一个大日志文件&#xff08;size > 1G&#xff09;解析的项目&#xff0c;在此记录下对于大文本解析方式的效率比较。不同方式的性能差别很大&#xff0c;那个项目的日志解析时间能从原来的超过36小时优化到只需要2分钟&#xff0c;awk功不可没。 bash 比较 bash脚本…

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 …

tomcat限制用域名访问 禁止 ip访问

有时候会遇到服务器网站。只可以通过域名访问。而不允许ip访问。防止域名恶意解析&#xff0c;tomcat可以实现这个简单功能。1&#xff0c;禁止ip访问项目 2&#xff0c;只允许绑定域名访问环境&#xff1a;tomcat7 外网地址&#xff1a;114.113.100.166 域名&#xff1a;bi…

Object关于属性property的静态方法

Object.defineProperty Object.defineProperty(obj, prop, { value: undefined, enumerable: true, writable:true, get: function() {return value}, set: function(newValue) {value newValue;} }) 当时配置了set和get时&#xff0c;则不能配置value。 Object.getOwnPropert…