主頁 > .NET開發 > 再記一次 應用服務器 CPU 暴高事故分析

再記一次 應用服務器 CPU 暴高事故分析

2021-02-09 07:56:14 .NET開發

一:背景

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

圖片名稱

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/258029.html

標籤:.NET技术

上一篇:聊聊如何使用單例模式

下一篇:搭建ASP.NET WEB API框架(1) 從零開始

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more