文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

再记一次 应用服务器 CPU 暴高事故分析

2024-12-03 11:56

关注

 一:背景

1. 前言

大概有2个月没写博客了,不是不想写哈??,关注公号的朋友应该知道我这两个月一直都在翻译文章,前前后后大概100篇左右吧,前几天看公号的 常读用户 降了好几十,心疼哈,还得回过神来继续写!

2. 讲故事

上周给 武汉同济 做项目升级,本以为一切顺利,结果捅娄子了,第二天上午高峰期运维说生产上两台 应用服务器 cpu 被打满,影响到所有客户使用,造成了大面积瘫痪,真尬尴,得先让运维抓一个 dump 下来再重启网站,还好,老板人可以,没有问责 ??。

二:CPU 爆高问题分析

1. 找思路

分析 dump,没什么比 windbg 更专业了,不过分析 dump 我还是比较拿手的,那怎么分析呢?最简单粗暴的做法就是看每一个线程当时都在做什么,进而推测一下就 八九不离十 了。

2. 查看所有线程栈

首先用 !t 和 !tp 看一下当前程序的 线程 和 线程池 的整体概况。

  1. 0:000> !t 
  2. ThreadCount:      60 
  3. UnstartedThread:  0 
  4. BackgroundThread: 38 
  5. PendingThread:    0 
  6. DeadThread:       22 
  7. Hosted Runtime:   no 
  8.                                                                          Lock   
  9.        ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   nc">Count Apt Exception 
  10.   11    1 2c24 02487038     28220 Preemptive  00000000:00000000 010df4f8 0     Ukn  
  11.   28    2 2ca0 024bad90     2b220 Preemptive  00000000:00000000 010df4f8 0     MTA (Finalizer)  
  12.   30    3 2d04 024f1450   102a220 Preemptive  00000000:00000000 010df4f8 0     MTA (Threadpool Worker)  
  13.   31    4 2054 024fb188     21220 Preemptive  00000000:00000000 010df4f8 0     Ukn  
  14.   32    6 1128 02574400   1020220 Preemptive  00000000:00000000 010df4f8 0     Ukn (Threadpool Worker)  
  15.    2    5 27ac 02520da8     20220 Preemptive  00000000:00000000 010df4f8 0     Ukn  
  16.   35   17 2c44 1cc362c8   202b220 Preemptive  00000000:00000000 024fa838 1     MTA  
  17.   36   20 1740 1cccc748     21220 Preemptive  00000000:00000000 010df4f8 0     Ukn  
  18.   37   21 16c4 1cc08240     21220 Preemptive  00000000:00000000 010df4f8 0     Ukn  
  19.   38   22 16a8 1ccd28b8     21220 Preemptive  00000000:00000000 010df4f8 0     Ukn  
  20.  
  21. .... 
  22.  
  23. 0:000> !tp 
  24. CPU utilization: 97% 
  25. Worker Thread: Total: 21 Running: 21 Idle: 0 MaxLimit: 8191 MinLimit: 8 
  26. Work Request in Queue: 23 
  27.     Unknown Function: 6d92a17f  Context: 0109b5f0 
  28.     Unknown Function: 6d92a17f  Context: 0107ed90 
  29.     Unknown Function: 6d92a17f  Context: 0104e750 
  30.     Unknown Function: 6d92a17f  Context: 010a0200 
  31.     AsyncTimerCallbackCompletion TimerInfo@207f8008 
  32.     AsyncTimerCallbackCompletion TimerInfo@0251b418 
  33.     Unknown Function: 6d92a17f  Context: 01096c78 
  34.     Unknown Function: 6d92a17f  Context: 01081398 
  35.     AsyncTimerCallbackCompletion TimerInfo@024d0120 
  36.     Unknown Function: 6d92a17f  Context: 010a9a20 
  37.     Unknown Function: 6d92a17f  Context: 01057950 
  38.     Unknown Function: 6d92a17f  Context: 0104c2d0 
  39.     Unknown Function: 6d92a17f  Context: 010943d8 
  40.     Unknown Function: 6d92a17f  Context: 0107a180 
  41.     Unknown Function: 6d92a17f  Context: 010a7418 
  42.     Unknown Function: 6d92a17f  Context: 010839a0 
  43.     Unknown Function: 6d92a17f  Context: 010678d0 
  44.     Unknown Function: 6d92a17f  Context: 010a2808 
  45.     Unknown Function: 6d92a17f  Context: 0105c250 
  46.     Unknown Function: 6d92a17f  Context: 0108abb8 
  47.     Unknown Function: 6d92a17f  Context: 0108f7c8 
  48.     Unknown Function: 6d92a17f  Context: 0108d1c0 
  49.     Unknown Function: 6d92a17f  Context: 20896498 
  50. -------------------------------------- 
  51. Number of Timers: 0 
  52. -------------------------------------- 
  53. Completion Port Thread:Total: 1 Free: 1 MaxFree: 16 CurrentLimit: 1 MaxLimit: 1000 MinLimit: 8 

从上面的输出大概可以看到如下几点信息:

总的来看,系统已经高负荷,不堪重负了,接下来的一个疑问就来了,所有的线程都被打满而且线程池中还有大量等待处理的任务在排队,现有的线程都在干嘛呢?难道不处理吗?

2. 查看所有线程的托管线程栈

既然现存的有 60 个线程,那我就使用 ~ *e !clrstack 命令看看所有的线程都在干嘛?

  1. 0:000> ~ *e !clrstack 
  2. OS Thread Id: 0x8d8 (44) 
  3. Child SP       IP Call Site 
  4. 1ad8d750 7759f901 [InlinedCallFrame: 1ad8d750]  
  5. 1ad8d74c 71e1a9ea DomainNeutralILStubClass.IL_STUB_PInvoke(IntPtr, IntPtr, System.String, System.String, Int32, Int32, System.String, Int32, Int32, Int32) 
  6. 1ad8d750 71d52f0b [InlinedCallFrame: 1ad8d750] System.Globalization.CompareInfo.InternalCompareString(IntPtr, IntPtr, System.String, System.String, Int32, Int32, System.String, Int32, Int32, Int32) 
  7. 1ad8d7b4 71d52f0b System.Globalization.CompareInfo.Compare(System.String, Int32, Int32, System.String, Int32, Int32, System.Globalization.CompareOptions) 
  8. 1ad8d7e0 71e16ab9 System.String.Compare(System.String, Int32, System.String, Int32, Int32, System.Globalization.CultureInfo, System.Globalization.CompareOptions) 
  9. 1ad8d810 71d51c8e System.Globalization.DateTimeFormatInfo.Tokenize(System.TokenType, System.TokenType ByRef, Int32 ByRef, System.__DTString ByRef) 
  10. 1ad8d86c 71d51a92 System.__DTString.GetSeparatorToken(System.Globalization.DateTimeFormatInfo, Int32 ByRef, Char ByRef) 
  11. 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) 
  12. 1ad8d8dc 71d50b59 System.DateTimeParse.TryParse(System.String, System.Globalization.DateTimeFormatInfo, System.Globalization.DateTimeStyles, System.DateTimeResult ByRef) 
  13. 1ad8d974 71dfce8b System.DateTimeParse.Parse(System.String, System.Globalization.DateTimeFormatInfo, System.Globalization.DateTimeStyles) 
  14. 1ad8d9d8 7243c9bc System.nc">Convert.ToDateTime(System.String, System.IFormatProvider) 
  15. 1ad8d9f8 724369b1 System.String.System.IConvertible.ToDateTime(System.IFormatProvider) 
  16. 1ad8da00 7243c8a2 System.nc">Convert.ToDateTime(System.Object) 

由于输出的太多,这里就简略输出了,不过我大体罗列了一下线程大概都在做什么事情。

9 + 4 个线程都在搞这两个方法,这就比较可疑了,不过从各个线程的栈顶上看并没有类似wait 关键词,这就意味着大家不是在争抢锁啥的,那问题在哪里呢?

继续分析这两个方法到底在数据库中读了什么?通过 !dso 抓取 GetDayInvoice() 方法中的 sql,这里我就模糊一下了哈,windbg命令大概如下:

  1. 0:000> ~45s 
  2. eax=1c06a5c8 ebx=00000000 ecx=59002090 edx=59002090 esi=000003d4 edi=00000000 
  3. eip=7759f901 esp=1d95cfa8 ebp=1d95d014 iopl=0         nv up ei pl zr na pe nc 
  4. cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246 
  5. ntdll!NtWaitForSingleObject+0x15: 
  6. 7759f901 83c404          add     esp,4 
  7. 0:045> !dso 
  8. OS Thread Id: 0x2a04 (45) 
  9. ESP/REG  Object   Name 
  10. 1D95D6D0 aaaac9f4 System.String    SELECT xxxxxx FROM xxxx as v WITH(NOLOCK) nc">left join xxx as cr WITH(NOLOCK) on v.xxx=cr.xxx nc">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 就可以了,如下代码所示:

  1. 0:045> !dumpheap -stat 
  2. The garbage collector data structures are not in a valid state for traversal. 
  3. It is either in the "plan phase," where objects are being moved around, or 
  4. we are at the initialization or shutdown of the gc heap. Commands related to  
  5. displaying, finding or traversing objects as well as gc heap segments may not  
  6. work properly. !dumpheap and !verifyheap may incorrectly complain of heap  
  7. consistency errors. 
  8. Object <exec cmd="!ListNearObj /d 02881000">02881000exec> 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 爆高,这里有几个概念需要大家了解下:

尤其是上面第三个概念,既然当前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 运行,据运维说,目前服务器存在不能建过多的应用程序域,导致多个网站公用一个程序域,表面上是运维弄错应用程序域,根子上还是没有彻底改造的决心!

本文转载自微信公众号「 一线码农聊技术」,可以通过以下二维码关注。转载本文请联系 一线码农聊技术公众号。

 

来源:一线码农聊技术内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯