記一次 .NET某酒店後臺服務 卡死分析

来源:https://www.cnblogs.com/huangxincheng/p/18201249
-Advertisement-
Play Games

一:背景 1. 講故事 停了一個月沒有更新文章了,主要是忙於寫 C#內功修煉系列的PPT,現在基本上接近尾聲,可以回頭繼續更新這段時間分析dump的一些事故報告,有朋友微信上找到我,說他們的系統出現了大量的http超時,程式不響應處理了,讓我幫忙看下怎麼回事,dump也抓到了。 二:WinDbg分析 ...


一:背景

1. 講故事

停了一個月沒有更新文章了,主要是忙於寫 C#內功修煉系列的PPT,現在基本上接近尾聲,可以回頭繼續更新這段時間分析dump的一些事故報告,有朋友微信上找到我,說他們的系統出現了大量的http超時,程式不響應處理了,讓我幫忙看下怎麼回事,dump也抓到了。

二:WinDbg分析

1. 為什麼會出現請求超時

既然超時說明server端不響應這個請求,繼而達到了超時時間的一種異常情況,所以首先要想到的就是 線程池的健康度,可以用 !tp 命令觀察,輸出如下:


0:000> !tp
CPU utilization: 0%
Worker Thread: Total: 537 Running: 537 Idle: 0 MaxLimit: 32767 MinLimit: 12
Work Request in Queue: 82
    Unknown Function: 00007fff566a17d0  Context: 0000020f08cbd658
    Unknown Function: 00007fff566a17d0  Context: 0000020f09acfa80
    Unknown Function: 00007fff566a17d0  Context: 0000020f08702198
    Unknown Function: 00007fff566a17d0  Context: 0000020f09ad9068
    Unknown Function: 00007fff566a17d0  Context: 0000020f09abffe8
    Unknown Function: 00007fff566a17d0  Context: 0000020f093c9948
    Unknown Function: 00007fff566a17d0  Context: 0000020f093cfd28
    Unknown Function: 00007fff566a17d0  Context: 0000020f093d9358
    Unknown Function: 00007fff566a17d0  Context: 0000020f093c34e8
    Unknown Function: 00007fff566a17d0  Context: 0000020f093dc568
    ...
--------------------------------------
Number of Timers: 2
--------------------------------------
Completion Port Thread:Total: 2 Free: 2 MaxFree: 24 CurrentLimit: 2 MaxLimit: 1000 MinLimit: 12

從上面的卦象看異常非常明顯,線程池總共有 537個工作線程都是處於運行狀態,相信有經驗的朋友應該一眼就知道是怎麼回事,專業術語叫:線程饑餓,並且線程池隊列也積壓了 82個 待處理的任務。

2. 線程為什麼會饑餓

線程饑餓的原因有更多,我特意問了下 chatgpt,列舉如下:

  1. 優先順序傾斜:如果某些線程的優先順序設置過高,而其他線程的優先順序設置過低,高優先順序的線程可能會長時間占用CPU資源,導致低優先順序線程無法獲得執行機會。
  2. 死鎖:當多個線程相互等待對方釋放資源時,可能會導致死鎖。在死鎖情況下,所有線程都無法繼續執行,從而導致線程饑餓。
  3. 資源競爭:多個線程競爭有限的資源(如共用記憶體、文件、網路連接等)時,可能會導致某些線程長時間無法獲取到所需的資源而處於饑餓狀態。
  4. 不公平的調度策略:調度器可能存在不公平的調度策略,導致某些線程無法獲得公平的CPU時間片,從而長時間無法執行。
  5. 線程阻塞:某些線程可能由於等待I/O操作、鎖或其他原因而被阻塞,如果阻塞時間過長,可能導致其他線程饑餓。
  6. 線程池配置不當:如果線程池中的線程數量設置不當,可能會導致某些任務長時間等待執行,從而引發線程饑餓。

那到底是哪一種情況呢?可以用 ~*e !clrstack 看一下各個線程此時正在做什麼,輸出如下:


0:000> ~*e !clrstack
...
OS Thread Id: 0x2924 (74)
        Child SP               IP Call Site
000000e0ef47dc30 00007fff60fd6974 [GCFrame: 000000e0ef47dc30] 
000000e0ef47dd58 00007fff60fd6974 [HelperMethodFrame_1OBJ: 000000e0ef47dd58] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)
000000e0ef47de70 00007ffef33e7269 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken)
000000e0ef47df00 00007ffef33e6b58 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken)
000000e0ef47df70 00007ffef33e69e1 System.Threading.Tasks.Task.InternalWait(Int32, System.Threading.CancellationToken)
000000e0ef47e040 00007ffef60cce33 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
000000e0ef47e070 00007ffef9df2c73 Exceptionless.Submission.DefaultSubmissionClient.SendHeartbeat(System.String, Boolean, Exceptionless.ExceptionlessConfiguration)
000000e0ef47e110 00007ffef109f03f System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
000000e0ef47e1e0 00007ffef109e784 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
000000e0ef47e210 00007ffef15b670b System.Threading.TimerQueueTimer.CallCallback()
000000e0ef47e270 00007ffef15b644d System.Threading.TimerQueueTimer.Fire()
000000e0ef47e2e0 00007ffef15b5613 System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
000000e0ef47e320 00007ffef10b8319 System.Threading.ThreadPoolWorkQueue.Dispatch()
000000e0ef47e7a0 00007fff4fa06993 [DebuggerU2MCatchHandlerFrame: 000000e0ef47e7a0] 
000000e0ef47e908 00007fff4fa06993 [ContextTransitionFrame: 000000e0ef47e908] 
000000e0ef47eb40 00007fff4fa06993 [DebuggerU2MCatchHandlerFrame: 000000e0ef47eb40] 
...

發現有 473 個線程都在 Exceptionless.Submission.DefaultSubmissionClient.SendHeartbeat 方法上進行等待,這就有意思了,原來是開源的日誌收集組件發送的心跳檢測方法,接下來趕緊看一下這個方法的源碼。


public void SendHeartbeat(string sessionIdOrUserId, bool closeSession, ExceptionlessConfiguration config)
{
	if (!config.IsValid)
	{
		return;
	}
	string requestUri = $"{GetHeartbeatServiceEndPoint(config)}/events/session/heartbeat?id={sessionIdOrUserId}&close={closeSession}";
	try
	{
		_client.Value.AddAuthorizationHeader(config.ApiKey);
		_client.Value.GetAsync(requestUri).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter()
			.GetResult();
	}
	catch (Exception exception)
	{
		config.Resolver.GetLog().Error("Error submitting heartbeat: " + exception.GetMessage());
	}
}

從源碼看,居然用同步的方式發送 http請求,在這非同步方法滿天飛的世界里,上面的寫法實屬異類。

3. 該如何解決呢?

既然是 Exceptionless 內部寫的 SendHeartbeat 方法,我們程式員基本上無法干預,能做到的無非如下兩點:

  • 升級框架

看下了用的還是超老的 4.3 版本,可以升級到目前最新的 6.0.4 觀察試試。


[assembly: AssemblyTitle("Exceptionless")]
[assembly: AssemblyProduct("Exceptionless")]
[assembly: AssemblyCompany("Exceptionless")]
[assembly: AssemblyTrademark("Exceptionless")]
[assembly: AssemblyCopyright("Copyright (c) 2017 Exceptionless.  All rights reserved.")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("4.3.2027.0")]
[assembly: AssemblyInformationalVersion("4.3.2027$(VERSION_SUFFIX) f8d73f2fd7")]
[assembly: TargetFramework(".NETFramework,Version=v4.5", FrameworkDisplayName = ".NET Framework 4.5")]
[assembly: AssemblyVersion("4.3.2027.0")]

  • 使用替代品,或者不用

哈哈,不用它,這是萬能的治根之法。

三:對線程註入速度的解答

1. 朋友提了一個疑問

我現在知道這個 url 某個時段可能響應出了問題,但我線程池裡的線程增速應該很快呀,多餘的線程不是可以響應客戶端請求嗎?為什麼我發現的情況是全部卡死呢?

2. 疑問的簡單解答

這個問題其實是考察對線程池底層的瞭解,尤其是多久會向線程池註入一個活線程,在 .NET Framework 時代,線上程饑餓的情況下線程池內部的 GateThread線程 會 1s 註入一個活線程,那如何驗證呢? 我們觀察後續的線程創建時間即可,使用 ~*e .ttime


0:000> ~*e .ttime
...
Created: Thu Nov 16 11:10:21.582 2023 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.000
Created: Thu Nov 16 11:10:22.593 2023 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.000
Created: Thu Nov 16 11:10:23.562 2023 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.000
Created: Thu Nov 16 11:10:24.062 2023 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.000
Created: Thu Nov 16 11:10:24.577 2023 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.000
Created: Thu Nov 16 11:10:25.562 2023 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.000
Created: Thu Nov 16 11:10:26.562 2023 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.015
Created: Thu Nov 16 11:10:27.562 2023 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.015
Created: Thu Nov 16 11:10:28.562 2023 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.015
Created: Thu Nov 16 11:10:29.577 2023 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.015
Created: Thu Nov 16 11:10:30.562 2023 (UTC + 8:00)
Kernel:  0 days 0:00:00.000
User:    0 days 0:00:00.000

從卦中的輸出來看,每一個 Created 大概差 1s 鐘,這也是 GateThread 的功勞,這種註入速度在 .NET8 中已經做了優化,比如上面這種情況,Task 內部會主動喚醒 GateThread 線程讓其立即註入新線程,從而提升程式的響應速度。

四:總結

很多時候分析下來發現是 第三方組件 拖垮了程式,自己又沒有太多的介入能力,真的很無奈,框架都用了那麼久,現在看到了一隻蒼蠅,已是食之無味,棄之可惜。
圖片名稱


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 0 前言 除非你一直生活在岩石下麵,否則你可能已經知道微服務是當今流行的架構趨勢。與這一趨勢一同成長,Segment早期就採用了這種最佳實踐,這在某些情況下對我們很有幫助,但正如你將很快瞭解到的,在其他情況下則並非如此。 簡單來說,微服務是一種面向服務的軟體架構,其中伺服器端應用程式通過組合許多單一 ...
  • 官方文檔: pathlib — Object-oriented filesystem paths 一、基礎使用 遍歷子目錄 使用通配符遍歷文件 拼接路徑 獲取標準化後的絕對路徑 查詢路徑常規屬性 打開文件 from pathlib import Path print('1.1 查詢指定目錄的子目錄' ...
  • title: 深入Django項目實戰與最佳實踐 date: 2024/5/19 21:41:38 updated: 2024/5/19 21:41:38 categories: 後端開發 tags: Django 基礎 項目實戰 最佳實踐 資料庫配置 靜態文件 部署 高級特性 第一章:Django ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .NET 中的表達式樹(Expression Trees) 表達式樹是什麼? 表達式樹(Expression Trees)是.NET框架中的一個強大功能,它將代碼表示為一個由表達式節點組成的樹形結構。每個節點代表代碼中的一個操作,例如方法調用、算術運算、邏輯運算等。表達式樹允許開發者在運行時分析、修 ...
  • Polly 是一個在 C# 中用於處理瞬態故障和提供彈性的庫。它允許你以聲明式的方式定義策略,如重試、熔斷、超時、回退等,這些策略可以幫助你的代碼在出現故障時保持穩健和可靠。 以下是如何在 C# 中使用 Polly 實現重試策略的基本步驟: 首先,你需要在你的項目中安裝 Polly 包。這可以通過 ...
  • EasyBlog 說明 本博客系統通過構建工具生成純靜態的博客網站,藉助GitHub Pages,你可以在5分鐘內免費擁有個人博客。 它具有以下特點 生成純靜態網站,訪問速度極快 使用markdown格式來編寫博客內容 基於git代碼管理來存儲你的博客 使用CI工具來自動化部署你的博客站點 效果展示 ...
  • 開始做項目管理了(本人3年java,來到這邊之後真沒想到...),天天開會溝通整理需求,他們講話的時候忙裡偷閑整理一下常用的方法,其實語言還是有共通性的,基本上看到方法名就大概能猜出來用法。出去打水的時候看到外面太陽好好,真想在外面坐著曬太陽,回來的時候好兄弟三年前送給我的鍵盤D鍵不靈了,在打"等待 ...
一周排行
    -Advertisement-
    Play Games
  • PasteSpider是什麼? 一款使用.net編寫的開源的Linux容器部署助手,支持一鍵發佈,平滑升級,自動伸縮, Key-Value配置,項目網關,環境隔離,運行報表,差量升級,私有倉庫,集群部署,版本管理等! 30分鐘上手,讓開發也可以很容易的學會在linux上部署你得項目! [從需求角度介 ...
  • SQLSugar是什麼 **1. 輕量級ORM框架,專為.NET CORE開發人員設計,它提供了簡單、高效的方式來處理資料庫操作,使開發人員能夠更輕鬆地與資料庫進行交互 2. 簡化資料庫操作和數據訪問,允許開發人員在C#代碼中直接操作資料庫,而不需要編寫複雜的SQL語句 3. 支持多種資料庫,包括但 ...
  • 在C#中,經常會有一些耗時較長的CPU密集型運算,因為如果直接在UI線程執行這樣的運算就會出現UI不響應的問題。解決這類問題的主要途徑是使用多線程,啟動一個後臺線程,把運算操作放在這個後臺線程中完成。但是原生介面的線程操作有一些難度,如果要更進一步的去完成線程間的通訊就會難上加難。 因此,.NET類 ...
  • 一:背景 1. 講故事 前些天有位朋友在微信上丟了一個崩潰的dump給我,讓我幫忙看下為什麼出現了崩潰,在 Windows 的事件查看器上顯示的是經典的 訪問違例 ,即 c0000005 錯誤碼,不管怎麼說有dump就可以上windbg開幹了。 二:WinDbg 分析 1. 程式為誰崩潰了 在 Wi ...
  • CSharpe中的IO+NPOI+序列化 文件文件夾操作 學習一下常見的文件、文件夾的操作。 什麼是IO流? I:就是input O:就是output,故稱:輸入輸出流 將數據讀入記憶體或者記憶體輸出的過程。 常見的IO流操作,一般說的是[記憶體]與[磁碟]之間的輸入輸出。 作用 持久化數據,保證數據不再 ...
  • C#.NET與JAVA互通之MD5哈希V2024 配套視頻: 要點: 1.計算MD5時,SDK自帶的計算哈希(ComputeHash)方法,輸入輸出參數都是byte數組。就涉及到字元串轉byte數組轉換時,編碼選擇的問題。 2.輸入參數,字元串轉byte數組時,編碼雙方要統一,一般為:UTF-8。 ...
  • CodeWF.EventBus,一款靈活的事件匯流排庫,實現模塊間解耦通信。支持多種.NET項目類型,如WPF、WinForms、ASP.NET Core等。採用簡潔設計,輕鬆實現事件的發佈與訂閱。通過有序的消息處理,確保事件得到妥善處理。簡化您的代碼,提升系統可維護性。 ...
  • 一、基本的.NET框架概念 .NET框架是一個由微軟開發的軟體開發平臺,它提供了一個運行時環境(CLR - Common Language Runtime)和一套豐富的類庫(FCL - Framework Class Library)。CLR負責管理代碼的執行,而FCL則提供了大量預先編寫好的代碼, ...
  • 本章將和大家分享在ASP.NET Core中如何使用高級客戶端NEST來操作我們的Elasticsearch。 NEST是一個高級別的Elasticsearch .NET客戶端,它仍然非常接近原始Elasticsearch API的映射。所有的請求和響應都是通過類型來暴露的,這使得它非常適合快速上手 ...
  • 參考delphi的代碼更改為C# Delphi 檢測密碼強度 規則(仿 google) 仿 google 評分規則 一、密碼長度: 5 分: 小於等於 4 個字元 10 分: 5 到 7 字元 25 分: 大於等於 8 個字元 二、字母: 0 分: 沒有字母 10 分: 全都是小(大)寫字母 20 ...