記一次 .NET 某電子病歷 CPU 爆高分析

来源:https://www.cnblogs.com/huangxincheng/archive/2022/10/12/16783304.html
-Advertisement-
Play Games

一:背景 1.講故事 前段時間有位朋友微信找到我,說他的程式出現了 CPU 爆高,幫忙看下程式到底出了什麼情況?圖就不上了,我們直接進入主題。 二:WinDbg 分析 1. CPU 真的爆高嗎? 要確認是否真的爆高,可以使用 !tp 觀察。 0:000> !tp CPU utilization: 9 ...


一:背景

1.講故事

前段時間有位朋友微信找到我,說他的程式出現了 CPU 爆高,幫忙看下程式到底出了什麼情況?圖就不上了,我們直接進入主題。

二:WinDbg 分析

1. CPU 真的爆高嗎?

要確認是否真的爆高,可以使用 !tp 觀察。


0:000> !tp
CPU utilization: 96%
Worker Thread: Total: 36 Running: 36 Idle: 0 MaxLimit: 32767 MinLimit: 16
Work Request in Queue: 61
    Unknown Function: 00007ffc5c461750  Context: 00000187da7a9788
    Unknown Function: 00007ffc5c461750  Context: 0000017fcdd36e88
    ...
    Unknown Function: 00007ffc5c461750  Context: 00000187da5e87d8
    Unknown Function: 00007ffc5c461750  Context: 00000187da872788
--------------------------------------
Number of Timers: 2
--------------------------------------
Completion Port Thread:Total: 1 Free: 1 MaxFree: 32 CurrentLimit: 1 MaxLimit: 1000 MinLimit: 16

從卦中可以看到 CPU=96%,果然是 CPU 爆高,而且 Work Request 也累積了 61 個任務未處理,看樣子下游不給力哈? 不給力有可能是因為 GC 觸發導致線程頻繁停頓,也可能真的是處理太慢。

2. 是 GC 觸發了嗎?

要查看是否真的 GC 觸發,可以用 !t -special 觀察下是否有 SuspendEE 字樣。


0:000> !t -special
ThreadCount:      83
UnstartedThread:  0
BackgroundThread: 74
PendingThread:    0
DeadThread:       9
Hosted Runtime:   no
                                                                                                        Lock  
       ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
  19    1 1c84 0000017abe10cf60    28220 Preemptive  0000000000000000:0000000000000000 0000017abe103f70 0     Ukn 
  ...
          OSID Special thread type
       26 1c78 DbgHelper 
       27 1328 GC SuspendEE 
       28 1e78 GC 
       29 1ffc GC 
       30 1de0 GC 

果不其然 27 號線程帶了 SuspendEE ,說明當前 GC 是觸髮狀態,接下來看下 27 號線程的非托管棧, 到底發生了什麼。


0:027> k
 # Child-SP          RetAddr               Call Site
00 00000074`11aff348 00007ffc`66624abf     ntdll!NtWaitForSingleObject+0x14
01 00000074`11aff350 00007ffc`591aa747     KERNELBASE!WaitForSingleObjectEx+0x8f
02 00000074`11aff3f0 00007ffc`591aa6ff     clr!CLREventWaitHelper2+0x3c
03 00000074`11aff430 00007ffc`591aa67c     clr!CLREventWaitHelper+0x1f
04 00000074`11aff490 00007ffc`59048ef5     clr!CLREventBase::WaitEx+0x7c
05 00000074`11aff520 00007ffc`5905370e     clr!SVR::t_join::join+0x10f
06 00000074`11aff580 00007ffc`59049278     clr!SVR::gc_heap::plan_phase+0x11f4
07 00000074`11aff900 00007ffc`590494d6     clr!SVR::gc_heap::gc1+0xb8
08 00000074`11aff950 00007ffc`59048c64     clr!SVR::gc_heap::garbage_collect+0x870
09 00000074`11aff9f0 00007ffc`59192487     clr!SVR::gc_heap::gc_thread_function+0x74
0a 00000074`11affa20 00007ffc`59194194     clr!SVR::gc_heap::gc_thread_stub+0x7e
0b 00000074`11affa60 00007ffc`694184d4     clr!GCThreadStub+0x24
0c 00000074`11affa90 00007ffc`69dee8b1     kernel32!BaseThreadInitThunk+0x14
0d 00000074`11affac0 00000000`00000000     ntdll!RtlUserThreadStart+0x21

從棧方法 gc_thread_function() 來看,這是一個專有的 GC 線程,熟悉 server GC 的朋友應該知道,用戶線程分配 引發GC後,會通過 event 喚醒GC線程,言外之意就是還沒有找到這個用戶線程觸發的導火索,要想找到答案有很多方法,查看當前的 GCSettings 觀察 GC 觸發的誘因及代數,截圖如下:

我去,居然是一個誘導式FullGC,言外之意就是有代碼會調用 GC.Collect() ,接下來我們用 ~*e !clrstack 導出所有的線程棧,觀察 GC.Collect() 字樣,還真給找到了。。。


0:117> !clrstack 
OS Thread Id: 0x170c (117)
        Child SP               IP Call Site
0000007419f1d580 00007ffc69e25ac4 [InlinedCallFrame: 0000007419f1d580] System.GC._Collect(Int32, Int32)
0000007419f1d580 00007ffbfba0fbf2 [InlinedCallFrame: 0000007419f1d580] System.GC._Collect(Int32, Int32)
0000007419f1d550 00007ffbfba0fbf2 Spire.Pdf.PdfDocument.Dispose()
...
0000007419f1e2f0 00007ffc504b1092 System.Web.Mvc.MvcHandler.EndProcessRequest(System.IAsyncResult)

從代碼看居然是一個商業組件 Spire.Pdf 在 Dispose 時手工釋放觸發的,一般這麼做的目的是想通過此方法間接釋放非托管資源。

其實一個 FullGC 不代表什麼,如果頻繁的 FullGC 肯定是有問題的,那如何觀察是否頻繁呢?在 CLR 源碼中有一個 full_gc_counts 的全局變數,記錄著FullGC 的次數,代碼如下:


size_t gc_heap::full_gc_counts[gc_type_max];

enum gc_type
{
    gc_type_compacting = 0,
    gc_type_blocking = 1,
#ifdef BACKGROUND_GC
    gc_type_background = 2,
#endif //BACKGROUND_GC
    gc_type_max = 3
};

接下來可以用 x 命令去檢索這個變數,觀察各自的佈局。

因為 gc_type_compactinggc_type_blocking 有重疊,而且觀察進程運行了 17min,所以 17min 觸發了至少 113 =90+23 次 FullGC。


0:117> .time
Debug session time: Tue Sep  6 15:56:08.000 2022 (UTC + 8:00)
System Uptime: 0 days 21:59:52.396
Process Uptime: 0 days 0:17:10.000
  Kernel time: 0 days 0:34:34.000
  User time: 0 days 0:39:05.000

這個算頻繁嗎?觸發點是否集中? 在DUMP這種照片下是不得而知的,為了穩一點再看看可有其他的線索。

3. 還有其他線索嗎?

既然線程池堆積了很多任務,除了受到一些諸如 GC 的外因影響,內因肯定是最主要的,既然都是 http 請求,可以用 !whttp 觀察各自的 HttpContext。


0:117> !whttp
HttpContext    Thread Time Out Running  Status Verb     Url
0000017b406b6f80  102 00:05:00 00:08:56    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017b46797110  107 00:05:00 00:07:35    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017b814572f8   97 00:05:00 00:08:49    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017b84634490  104 00:05:00 00:07:46    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017bc04767b0   90 00:05:00 00:08:43    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017e3e79cbb8   96 00:05:00 00:09:45    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017e7ee10b80   88 00:05:00 00:09:40    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017e89b2cfb0  109 00:05:00 00:04:37    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017e8adb6b80  106 00:05:00 00:02:53    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017d41e90f28  103 00:05:00 00:08:04    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017d4385d528  101 00:05:00 00:07:39    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017d471b7d58   98 00:05:00 00:06:50    200 GET      /xxxx/xxx/xxxLogOutputExcel
0000017bc8283c48  117 00:05:00 00:00:32    200 GET      /xxx/xxx/xxxMedTags
...

從卦中看,有兩點信息:

  1. 高達 17 個 Excel 導出請求,一般來說導出操作都是 CPU 密集型的, 17 個請求可能剛好把 CPU 全部打滿,可以通過 !cpuid 驗證下。

0:117> !cpuid
CP  F/M/S  Manufacturer     MHz
 0  6,79,1  <unavailable>   1995
 1  6,79,1  <unavailable>   1995
 2  6,79,1  <unavailable>   1995
 3  6,79,1  <unavailable>   1995
 4  6,79,1  <unavailable>   1995
 5  6,79,1  <unavailable>   1995
 6  6,79,1  <unavailable>   1995
 7  6,79,1  <unavailable>   1995
 8  6,79,1  <unavailable>   1995
 9  6,79,1  <unavailable>   1995
10  6,79,1  <unavailable>   1995
11  6,79,1  <unavailable>   1995
12  6,79,1  <unavailable>   1995
13  6,79,1  <unavailable>   1995
14  6,79,1  <unavailable>   1995
15  6,79,1  <unavailable>   1995

  1. 觸發 GC 的請求是 /xxx/xxx/xxxMedTags 也高達 32s ,說明程式此時整體變慢。

接下來就是把挖到的這兩點信息告訴朋友,重點是 xxxLogOutputExcel 導出,一定要限定頻次。

三:總結

總體來說這次生產事故誘發的因素有兩個:

  • 主因是客戶高頻次的點擊 Excel 導出,越著急越點,越點越著急,導致系統的雪崩。

  • 高頻的Excel點擊操作,間接導致 Spire.Pdf 在某一時段為了釋放非托管資源頻發的誘導 GC.Collect,進而雪上加霜。

解決方案就簡單了,抑制高頻點擊。

多一點耐心,少一點急躁,也許我們相處的會更好。

圖片名稱
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 如題,作者一開始也遇到了這個比較棘手的問題。 話不多說,直接說解決方案。 這裡使用bytestring,如果是object[]的話則用repeated定義即可。 那麼問題又來了,用這個類型怎麼做到與java中的object完全轉換呢? 目前,作者發現bytestring對於java的object沒有 ...
  • 需求背景: 要求導出的Excel有水印,且水印不要是可點擊的圖片影響正常操作。 開發環境: JDK 1.8 SpringBoot v2.2.5.RELEASE 導入依賴: 1 <!-- poi 相關--> 2 <dependency> 3 <groupId>org.apache.poi</group ...
  • ##HashMap Map集合基於 鍵(key)/值(value)映射。每個鍵最多只能映射一個值。鍵可以是任何引用數據類型的值,不可重覆;值可以是任何引用數據類型的值,可以重覆;鍵值對存放無序。 HashMap常用方法 ##put/get 方法 1.put(K key, V value) 將鍵(ke ...
  • 今天幫同學解決scrapy的時候發現的,就是在我這裡能運行,在他那裡不能運行。對比scrapy的版本發現,他的是最新的(2.6.3),然後我的是2.5.0的。隨後我將他的版本也換成跟我同樣的,他的也能運行了。之後我去看了官方更新文檔,發現在2.5.1的更新上說明瞭,http的驗證需要在setting ...
  • 依賴註入 控制反轉(inversion of control,IOC)是設計模式中非常重要的思想,而依賴註入(dependency injection,DI)是控制反轉思想的一種重要的實現方式。依賴註入簡化了模塊的組裝過程,減小了模塊之間的耦合度,因此.NET Core中大量應用了依賴註入的開發模式 ...
  • 需求:在開發過程中經常需要部分數據等待的載入。可能因為某個表比較慢而卡到主線程,所以需要實現局部載入等待。(在Web肯定就ajax非同步就好做,winform就比較複雜點了) 效果圖 效果圖的主要效果沒展示出來,就這樣吧。懶了懶了! 用用戶控制項新建等待視窗 //Loding2的類 public par ...
  • 在最新版的 .NET 平臺中,微軟在逐步放棄 System.Drawing.Imaging ,給出的理由如下: System.Drawing命名空間對某些操作系統和應用程式類型有一些限制。 在Windows, System.Drawing 依賴於GDI+操作系統附帶的本機庫。 某些Windows S ...
  • 支持.NET/.NET Framework/.NET Core RabbitMQ作為一款主流的消息隊列工具早已廣受歡迎。相比於其它的MQ工具,RabbitMQ支持的語言更多、功能更完善。 本文提供一種市面上最/極簡單的使用RabbitMQ的方式,只需要會調用以下三個方法,你就幾乎可以掌握整個Rabb ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...