再记一次 应用服务器 CPU 暴高事故分析
一:背景
1. 前言
大概有2个月没写博客了,不是不想写哈,关注公号的朋友应该知道我这两个月一直都在翻译文章,前前后后大概100篇左右吧,前几天看公号的 常读用户
降了好几十,心疼哈,还得回过神来继续写!
2. 讲故事
上周给 武汉同济
做项目升级,本以为一切顺利,结果捅娄子了,第二天上午高峰期运维说生产上两台 应用服务器 cpu 被打满,影响到所有客户使用,造成了大面积瘫痪,真尬尴,得先让运维抓一个 dump 下来再重启网站,还好,老板人可以,没有问责 。
二:CPU 爆高问题分析
1. 找思路
分析 dump,没什么比 windbg 更专业了,不过分析 dump 我还是比较拿手的,那怎么分析呢? 最简单粗暴的做法就是看每一个线程当时都在做什么,进而推测一下就 八九不离十
了。
2. 查看所有线程栈
首先用 !t
和 !tp
看一下当前程序的 线程
和 线程池
的整体概况。
0:000> !t
ThreadCount: 60
UnstartedThread: 0
BackgroundThread: 38
PendingThread: 0
DeadThread: 22
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
11 1 2c24 02487038 28220 Preemptive 00000000:00000000 010df4f8 0 Ukn
28 2 2ca0 024bad90 2b220 Preemptive 00000000:00000000 010df4f8 0 MTA (Finalizer)
30 3 2d04 024f1450 102a220 Preemptive 00000000:00000000 010df4f8 0 MTA (Threadpool Worker)
31 4 2054 024fb188 21220 Preemptive 00000000:00000000 010df4f8 0 Ukn
32 6 1128 02574400 1020220 Preemptive 00000000:00000000 010df4f8 0 Ukn (Threadpool Worker)
2 5 27ac 02520da8 20220 Preemptive 00000000:00000000 010df4f8 0 Ukn
35 17 2c44 1cc362c8 202b220 Preemptive 00000000:00000000 024fa838 1 MTA
36 20 1740 1cccc748 21220 Preemptive 00000000:00000000 010df4f8 0 Ukn
37 21 16c4 1cc08240 21220 Preemptive 00000000:00000000 010df4f8 0 Ukn
38 22 16a8 1ccd28b8 21220 Preemptive 00000000:00000000 010df4f8 0 Ukn
....
0:000> !tp
CPU utilization: 97%
Worker Thread: Total: 21 Running: 21 Idle: 0 MaxLimit: 8191 MinLimit: 8
Work Request in Queue: 23
Unknown Function: 6d92a17f Context: 0109b5f0
Unknown Function: 6d92a17f Context: 0107ed90
Unknown Function: 6d92a17f Context: 0104e750
Unknown Function: 6d92a17f Context: 010a0200
AsyncTimerCallbackCompletion TimerInfo@207f8008
AsyncTimerCallbackCompletion TimerInfo@0251b418
Unknown Function: 6d92a17f Context: 01096c78
Unknown Function: 6d92a17f Context: 01081398
AsyncTimerCallbackCompletion TimerInfo@024d0120
Unknown Function: 6d92a17f Context: 010a9a20
Unknown Function: 6d92a17f Context: 01057950
Unknown Function: 6d92a17f Context: 0104c2d0
Unknown Function: 6d92a17f Context: 010943d8
Unknown Function: 6d92a17f Context: 0107a180
Unknown Function: 6d92a17f Context: 010a7418
Unknown Function: 6d92a17f Context: 010839a0
Unknown Function: 6d92a17f Context: 010678d0
Unknown Function: 6d92a17f Context: 010a2808
Unknown Function: 6d92a17f Context: 0105c250
Unknown Function: 6d92a17f Context: 0108abb8
Unknown Function: 6d92a17f Context: 0108f7c8
Unknown Function: 6d92a17f Context: 0108d1c0
Unknown Function: 6d92a17f Context: 20896498
--------------------------------------
Number of Timers: 0
--------------------------------------
Completion Port Thread:Total: 1 Free: 1 MaxFree: 16 CurrentLimit: 1 MaxLimit: 1000 MinLimit: 8
从上面的输出大概可以看到如下几点信息:
当前程序有 60 个线程。
当前 CPU 利用率为 97%,爆高无疑。
线程池中有21个线程全部打满,还有23个任务在
任务队列
中排队等待处理。
总的来看,系统已经高负荷,不堪重负了,接下来的一个疑问就来了,所有的线程都被打满而且线程池中还有大量等待处理的任务在排队,现有的线程都在干嘛呢?难道不处理吗?
2. 查看所有线程的托管线程栈
既然现存的有 60 个线程,那我就使用 ~ *e !clrstack
命令看看所有的线程都在干嘛?
0:000> ~ *e !clrstack
OS Thread Id: 0x8d8 (44)
Child SP IP Call Site
1ad8d750 7759f901 [InlinedCallFrame: 1ad8d750]
1ad8d74c 71e1a9ea DomainNeutralILStubClass.IL_STUB_PInvoke(IntPtr, IntPtr, System.String, System.String, Int32, Int32, System.String, Int32, Int32, Int32)
1ad8d750 71d52f0b [InlinedCallFrame: 1ad8d750] System.Globalization.CompareInfo.InternalCompareString(IntPtr, IntPtr, System.String, System.String, Int32, Int32, System.String, Int32, Int32, Int32)
1ad8d7b4 71d52f0b System.Globalization.CompareInfo.Compare(System.String, Int32, Int32, System.String, Int32, Int32, System.Globalization.CompareOptions)
1ad8d7e0 71e16ab9 System.String.Compare(System.String, Int32, System.String, Int32, Int32, System.Globalization.CultureInfo, System.Globalization.CompareOptions)
1ad8d810 71d51c8e System.Globalization.DateTimeFormatInfo.Tokenize(System.TokenType, System.TokenType ByRef, Int32 ByRef, System.__DTString ByRef)
1ad8d86c 71d51a92 System.__DTString.GetSeparatorToken(System.Globalization.DateTimeFormatInfo, Int32 ByRef, Char ByRef)
1ad8d88c 71d513c4 System.DateTimeParse.Lex(DS, System.__DTString ByRef, System.DateTimeToken ByRef, System.DateTimeRawInfo ByRef, System.DateTimeResult ByRef, System.Globalization.DateTimeFormatInfo ByRef, System.Globalization.DateTimeStyles)
1ad8d8dc 71d50b59 System.DateTimeParse.TryParse(System.String, System.Globalization.DateTimeFormatInfo, System.Globalization.DateTimeStyles, System.DateTimeResult ByRef)
1ad8d974 71dfce8b System.DateTimeParse.Parse(System.String, System.Globalization.DateTimeFormatInfo, System.Globalization.DateTimeStyles)
1ad8d9d8 7243c9bc System.Convert.ToDateTime(System.String, System.IFormatProvider)
1ad8d9f8 724369b1 System.String.System.IConvertible.ToDateTime(System.IFormatProvider)
1ad8da00 7243c8a2 System.Convert.ToDateTime(System.Object)
由于输出的太多,这里就简略输出了,不过我大体罗列了一下线程大概都在做什么事情。
- 有 9 个线程正在执行
GetAllByCondition()
方法
- 有 4 个线程正在执行
GetDayInvoice()
方法
9 + 4
个线程都在搞这两个方法,这就比较可疑了,不过从各个线程的栈顶上看并没有类似 wait
关键词,这就意味着大家不是在争抢锁啥的,那问题在哪里呢?
继续分析这两个方法到底在数据库中读了什么? 通过 !dso
抓取 GetDayInvoice()
方法中的 sql,这里我就模糊一下了哈,windbg命令大概如下:
0:000> ~45s
eax=1c06a5c8 ebx=00000000 ecx=59002090 edx=59002090 esi=000003d4 edi=00000000
eip=7759f901 esp=1d95cfa8 ebp=1d95d014 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!NtWaitForSingleObject+0x15:
7759f901 83c404 add esp,4
0:045> !dso
OS Thread Id: 0x2a04 (45)
ESP/REG Object Name
1D95D6D0 aaaac9f4 System.String SELECT xxxxxx FROM xxxx as v WITH(NOLOCK) left join xxx as cr WITH(NOLOCK) on v.xxx=cr.xxx left join xxx as crr WITH(NOLOCK) on cr.PID=crr.ID WHERE xxx IN (SELECT DISTINCT xxx FROM xxxx WITH(NOLOCK) WHERE (SendTime>='2021-01-21 14:30:39' OR xxx>='2021-01-21 14:30:39' OR xxx>='2021-01-21 14:30:39') AND ((InvoiceType =1 and( IsRepeat=0 or IsRepeat is null )) OR xxx IN(16,15)) )
然后让运维查了下,这条sql大概有 13w
的记录,我第一反应就是查这么大的数据是不是有毛病撒,问了下懂业务的老大,这一块原来是做 初始化同步
, 而且这块好久都没人动过,言外之意原来也是这么查的,一点毛病也没有呀,我去,说的也是哈,为啥以前没问题呢???
3. 查看托管堆
既然一条sql查了 13w 条数据,刚才是 4个线程在执行 GetDayInvoice()
,也就意味着有 52w 条数据正在从 sqlserver 中获取,接下来我的本能反应就是看看托管堆,使用 !dumpheap -stat
就可以了,如下代码所示:
0:045> !dumpheap -stat
The garbage collector data structures are not in a valid state for traversal.
It is either in the "plan phase," where objects are being moved around, or
we are at the initialization or shutdown of the gc heap. Commands related to
displaying, finding or traversing objects as well as gc heap segments may not
work properly. !dumpheap and !verifyheap may incorrectly complain of heap
consistency errors.
Object <exec cmd="!ListNearObj /d 02881000">02881000</exec> has an invalid method table.
我去,有点尴尬,居然报错了,先擦擦头上的汗,这句话: The garbage collector data structures are not in a valid state for traversal
引起了我的警觉,当前托管堆是无效状态,gc被触发了。。。当前还是 plan phase
状态,是不是 gc 导致了 cpu 爆高呢?
4. 零碎信息整合
通过上面这些零碎的信息,不知道大家可整明白了,让我来说的话,简而言之: GetDayInvoice()
读了大量数据,导致gc频繁回收,进而导致 cpu 爆高,这里有几个概念需要大家了解下:
这个程序是 32bit,意味着最大只能吃 4G 内存。
32bit 的临时代( 0+1 代) 大概 几十M 的空间。
IIS 是
服务器模式
的GC,意味着当前的托管线程会临时充当 GC 回收线程。
尤其是上面第三个概念,既然当前gc被触发了,也就意味着托管线程被临时给 GC 征用了,那我可以看下是否真的是这样,可以用 ~ *e !dumpstack
查看所有线程的托管和非托管的所有栈信息,如下图所示:
可以清晰的看到,调用 GetDayInvoice()
的线程在 CLR 层面触发了 gc,而此时 gc 想调用 try_allocate_more_space
来给自己分配更多的空间,而且 wait_for_gc_done
也表示它在等待其他gc线程处理完成,所以就卡死在这里了。
如果不信的话,还可以继续观察其他线程的非托管堆栈,如下图所示:
从 clr!SVR::GCHeap::WaitUntilGCComplete+0x35, calling clr!CLREventBase::WaitEx
可以看出,都是在等待 GC 完成,导致 CPU 爆高。
5. 找到问题根源
汇总一下就是:这次 cpu 爆高是因为 32bit 程序只能吃 4G 内存,而程序需要同步大量的数据,导致内存被打满,gc无法分配更多的内存让程序使用,gc的机制让 cpu 打的满满的,知道问题之后,解决办法很简单,将 iis 的 应用程序域
的配置 启用32bit应用程序
改成 False 即可,这样程序就可以以 64bit 方式运行,如下图所示:
三:总结
很显然这次事件是因为管理混乱造成的,因为历史遗留问题,有些网站必须用 32 bit 运行,有些必须用 64 bit 运行,据运维说,目前服务器存在不能建过多的应用程序域,导致多个网站公用一个程序域,表面上是运维弄错应用程序域,根子上还是没有彻底改造的决心!
更多高质量干货:参见我的 GitHub: dotnetfly
再记一次 应用服务器 CPU 暴高事故分析的更多相关文章
- Windows服务器java.exe占用CPU过高问题分析及解决
最近在测试一个用java语言实现的数据采集接口时发现,接口一旦运行起来,CPU利用率瞬间飙升到85%-95%,一旦停止就恢复到40%以下,这让我不得不面对以前从未关注过的程序性能问题. 在硬着头皮查找 ...
- systemd --user进程CPU占用高问题分析
1.问题由来 近期发现堡垒机环境有如下问题,systemd占用大量cpu: 原文链接:https://www.cnblogs.com/yaohong/p/16046670.html 2.问题定位 2. ...
- db2 cpu使用率高问题分析处理
性能调优步骤 明确问题->收集数据->分析数据->细化.定位问题->优化 环境: db2 问题:%usr CPU高,大约99%,db2sysc进程使用的最多 收集数据 ---系 ...
- HBase Thrift2 CPU过高问题分析
目录 目录 1 1. 现象描述 1 2. 问题定位 2 3. 解决方案 5 4. 相关代码 5 1. 现象描述 外界连接9090端口均超时,但telnet端口总是成功.使用top命令观察,发现单个线程 ...
- Linux 线程占用CPU过高定位分析
今天朋友问我一个Linux程序CPU占用涨停了,该如何分析, CPU占用过高,模拟CPU占用过高的情况 先上一段代码: #include <iostream> #include <t ...
- 记一次某制造业ERP系统 CPU打爆事故分析
一:背景 1.讲故事 前些天有位朋友微信找到我,说他的程序出现了CPU阶段性爆高,过了一会就下去了,咨询下这个爆高阶段程序内部到底发生了什么? 画个图大概是下面这样,你懂的. 按经验来说,这种情况一般 ...
- MongoDB CPU 利用率高,分析慢请求
Jemeter 压测过程,发现mongodb的CPU均达到100%,需要查看mongodb的执行情况,使用mongo自带的profiling功能. profiling将请求的执行情况记录在DB下的 s ...
- 【JVM调优系列】----CPU过高的分析与解决方案
1.首先通过top命令查询当前进程所占cpu的一个比重
- 偶遇 smon 进程cpu 开销高异常分析
今天突然发现线上一台oracle 数据库 servercpu 跑的非常高.感觉不是非常正常,细致看了下.发现是smon 进程吃掉了一个cpu. 那么这个smon 进程究竟在倒腾啥玩意 对smon 进程 ...
随机推荐
- 【Linux】Linux进程间通信的几种方式
一.进程间通信的目的 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间 共享数据:多个进程要操作共享数据,一个进程对共享数据 信息传递:一个进程需要向另一个进程发 ...
- 敏感信息泄露 - Pikachu
概述: 由于后台人员的疏忽或者不当的设计,导致不应该被前端用户看到的数据被轻易的访问到. 比如:---通过访问url下的目录,可以直接列出目录下的文件列表;---输入错误的url参数后报错信息里面包含 ...
- luoguP2016 战略游戏
题目描述 Bob喜欢玩电脑游戏,特别是战略游戏.但是他经常无法找到快速玩过游戏的办法.现在他有个问题.他要建立一个古城堡,城堡中的路形成一棵树.他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能了 ...
- 1.2V升3.3V芯片,大电流,应用MCU供电,3.3V稳压源
MCU供电一般是2.5V-5V之间等等都有,1.2V需要升到3.3V的升压芯片来稳压输出3.3V给MCU供电. 同时1.2V的输入电压低,说明供电端的能量也是属于低能量的,对于芯片自身供货是也要求高. ...
- 使用Canal作为mysql的数据同步工具
一.Canal介绍 1.应用场景 在前面的统计分析功能中,我们采取了服务调用获取统计数据,这样耦合度高,效率相对较低,目前我采取另一种实现方式,通过实时同步数据库表的方式实现,例如我们要统计每天注册与 ...
- 关于springboot2.X使用外部tomcat服务器进行部署的操作详细步骤
1.修改pom.xml文件(4个地方) ①<packaging>war</packaging>将其中的jar该为war ②<dependency> <grou ...
- 什么是Etcd,如何运维Etcd ?
介绍 ETCD 是一个分布式.可靠的 key-value 存储的分布式系统,用于存储分布式系统中的关键数据:当然,它不仅仅用于存储,还提供配置共享及服务发现:基于Go语言实现. ETCD的特点 简单: ...
- 浅谈java中线程和操作系统线程
在聊线程之前,我们先了解一下操作系统线程的发展历程,在最初的时候,操作系统没有进程线程一说,执行程序都是串行方式执行,就像一个队列一样,先执行完排在前面的,再去执行后面的程序,这样的话很多程序的响应就 ...
- Java int和integer有什么区别 (mybatis踩坑)
不要在实体类中使用int 我们都知道Integer是int的包装类,而int是基本数据类型.所以Integer类型的变量会初始化为null,int类型则会被初始化为0 . 所以在下面的动态拼接例子中: ...
- jQuery 留言表单验证
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...