如何在.NET應用程序中分析CPU使用率過高的問題

原文來自互聯網,由長沙DotNET技術社區編譯。如譯文侵犯您的署名權或版權,請聯繫小編,小編將在24小時內刪除。限於譯者的能力有限,個別語句翻譯略顯生硬,還請見諒。

作者:胡安·帕勃羅·希達,JUAN PABLO SCIDA是一位軟件架構師,在軟件開發方面擁有10多年的經驗。他是經過認證的.NET和Java開發人員。在過去的幾年中,他還熱衷於使用Node.js,MongoDB和Erlang。

原文來自:https://www.toptal.com/dot-net/hunting-high-cpu-usage-in-dot-net

軟件開發可能是一個非常複雜的過程。作為開發人員,我們需要考慮很多不同的變量。有些不在我們的控制之下,有些在實際代碼執行時對我們來說是未知的,有些則由我們直接控制。 .NET開發人員[1]也毫不例外。

考慮到這樣的現實情況,當我們在受控環境中工作時,事情通常會按計劃進行。假設就是我們的開發機器或我們可以完全訪問的集成環境。我們可以使用工具來分析影響我們的代碼和軟件的不同變量。我們也不必處理服務器的繁重負載,也不必處理併發用戶嘗試同時執行相同操作的情況。

在可描述和安全的情況下,我們的代碼通常可以正常工作,但是在生產環境下,如果處於過度負載或其他一些外部因素的影響,可能會發生意外問題。生產環境的軟件性能很難分析。在大多數情況下,我們必須在理論上處理潛在的問題:我們知道可能會發生問題,但無法測試。這就是為什麼我們需要以我們所用語言的最佳實踐和文檔為基礎進行開發,並避免常見錯誤

[2]

如前所述,當軟件上線時,可能會出錯,並且代碼可能會以我們未計劃的方式開始執行。當我們不得不處理問題而又無法調試或確定發生了什麼情況時,下我們該怎麼辦?

如何在.NET应用程序中分析CPU使用率过高的问题

如果某個進程長時間使用超過90%的CPU,則我們會遇到麻煩

在本文中,我們將分析基於Windows的服務器上. net web應用程序的高CPU使用率的實際案例場景、涉及到的識別問題的過程,以及更重要的問題,為什麼會出現這個問題以及我們如何解決它。

CPU使用率和內存消耗是廣泛討論的主題。通常,很難確定某個特定進程應使用的資源(CPU,RAM,I / O)的正確數量以及持續的時間段。儘管可以肯定的是-如果某個進程長時間使用了超過90%的CPU,那麼我們將特別麻煩,因為在這種情況下服務器將無法處理任何其他請求。

這是否意味著流程本身存在問題?不必要。該過程可能需要更多的處理能力,或者正在處理大量數據。首先,我們唯一能做的就是嘗試確定發生這種情況的原因。

所有操作系統都有幾種不同的工具來監視服務器中發生的事情。Windows服務器專門具有任務管理器Performance Monitor[3],在本例中,我們使用了New Relic Servers[4],它是監視服務器的絕佳工具。

最初症狀和問題分析

部署應用程序後,在頭兩週的時間裡,我們開始看到服務器的CPU使用率達到峰值,這使服務器無響應。為了使其再次可用,我們必須重新啟動它,並且該事件在該時間段內發生了3次。如前所述,我們使用New Relic Servers作為服務器監視器,它表明w3wp.exe在服務器崩潰時,該進程佔用了94%的CPU。

Internet信息服務(IIS)工作進程是Windows進程(w3wp.exe),它運行Web應用程序,並負責處理發送到特定應用程序池的Web服務器的請求。IIS服務器可能有多個應用程序池(和幾個不同的w3wp.exe進程),這些池可能會產生問題。根據該進程具有的用戶(這在New Relic報告中顯示),我們確定問題出在我們的.NET C#Web表單舊版應用程序。

.NET Framework與Windows調試工具緊密集成在一起,因此,我們要做的第一件事是查看事件查看器和應用程序日誌文件,以查找有關正在發生的事情的有用信息。無論我們是否在事件查看器中記錄了一些異常,它們都沒有提供足夠的數據來進行分析。這就是為什麼我們決定更進一步並收集更多數據的原因,因此當事件再次發生時,我們將做好準備。

數據採集

收集用戶模式進程轉儲的最簡單方法是使用Debug Diagnostic Tools v2.0

[5]或僅使用DebugDiag。DebugDiag具有一組用於收集數據(DebugDiag集合)和分析數據(DebugDiag分析)的工具。

因此,讓我們開始定義使用調試診斷工具收集數據的規則:

1.打開DebugDiag集合,然後選擇Performance。

如何在.NET应用程序中分析CPU使用率过高的问题

2.選擇Performance Counters並單擊Next。3.點擊Add Perf Triggers。4.展開Processor(不是Process)對象,然後選擇% Processor Time。請注意,如果您使用的是Windows Server 2008 R2,並且具有64個以上的處理器,請選擇該Processor Information對象而不是該Processor對象。5.在實例列表中,選擇_Total。6.單擊Add,然後單擊確定OK。7.選擇新添加的觸發器,然後單擊確定Edit Thresholds。

如何在.NET应用程序中分析CPU使用率过高的问题

8.Above在下拉菜單中選擇。

9.將閾值更改為80。

10.輸入20秒數。

您可以根據需要調整該值,但請注意不要指定小數秒,以防止錯誤觸發。

如何在.NET应用程序中分析CPU使用率过高的问题

11.點擊OK。

12.點擊Next。

13.點擊Add Dump Target。

14.Web Application Pool從下拉菜單中選擇。

15.從應用程序池列表中選擇您的應用程序池。

16.點擊OK。

17.點擊Next。

18.Next再點擊一次。

19.如果需要,請輸入規則名稱,並記下轉儲的保存位置。

您可以根據需要更改此位置。

20.點擊Next。

21.選擇Activate the Rule Now並單擊Finish。

描述的規則將創建一組小型轉儲文件,這些文件的大小將非常小。最終轉儲將是具有完整內存的轉儲,並且該轉儲會更大。現在,我們只需要等待高CPU事件再次發生即可。

將轉儲文件保存在所選文件夾中後,我們將使用DebugDiag Analysis工具來分析收集的數據:

1.選擇性能分析器。

如何在.NET应用程序中分析CPU使用率过高的问题

2.添加轉儲文件。

如何在.NET应用程序中分析CPU使用率过高的问题

3.開始分析。

DebugDiag將花費幾分鐘(或數分鐘)來解析轉儲並提供分析。完成分析後,您將看到一個網頁,其中包含摘要以及有關線程的大量信息,類似於以下內容:

如何在.NET应用程序中分析CPU使用率过高的问题

正如您在摘要中看到的那樣,有一條警告說:“在一個或多個線程上檢測到轉儲文件之間的CPU使用率過高。” 如果單擊建議,我們將開始瞭解應用程序存在問題的地方。我們的示例報告如下所示:

如何在.NET应用程序中分析CPU使用率过高的问题

正如我們在報告中看到的那樣,有一個關於CPU使用率的模式。所有CPU使用率高的線程都與同一類相關。在跳到代碼之前,讓我們看一下第一個。

如何在.NET应用程序中分析CPU使用率过高的问题

這是我們遇到的第一個線程的細節。對我們來說有趣的部分是:

如何在.NET应用程序中分析CPU使用率过高的问题

在這裡,我們有一個代碼調用,GameHub.OnDisconnected該代碼觸發了有問題的操作,但是在此調用之前,我們有兩個Dictionary調用,它們可以使您對發生的事情有所瞭解。讓我們看一下.NET代碼,看看該方法在做什麼:

public override Task OnDisconnected {

<code>try/<code>
<code>{/<code>
<code>var userId = GetUserId;/<code>
<code>string connId;/<code>
<code>if(onlineSessions.TryGetValue(userId, out connId))/<code>
<code> onlineSessions.Remove(userId);/<code>
<code>}/<code>
<code>catch(Exception)/<code>

<code>{/<code>
<code>// ignored/<code>
<code>}/<code>
<code>returnbase.OnDisconnected;/<code>
<code>}/<code>

我們顯然在這裡有問題。報告的調用堆棧說問題出在字典上,在這段代碼中我們正在訪問字典,特別是引起問題的那一行是:

if (onlineSessions.TryGetValue(userId, out connId))

static Dictionary onlineSessions = new Dictionary;

.NET代碼有什麼問題?

具有面向對象編程經驗的每個人都知道靜態變量將由此類的所有實例共享。讓我們更深入地瞭解.NET世界中靜態的含義。

根據.NET C#規範:

使用static[6]修飾符聲明一個靜態成員,該成員屬於類型本身而不是特定對象。

這就是.NET C#語言規範關於靜態類和成員的說明[7]

與所有類類型一樣,當加載引用該類的程序時,.NET Framework公共語言運行庫(CLR)將加載靜態類的類型信息。程序無法確切指定何時加載類。但是,可以保證在程序中首次引用該類之前,將其加載並初始化其字段並調用其靜態構造函數。靜態構造函數僅被調用一次,並且靜態類在程序所在的應用程序域的生存期內保留在內存中。非靜態類可以包含靜態方法,字段,屬性或事件。即使沒有創建該類的實例,該靜態成員也可以在該類上調用。始終通過類名稱而不是實例名稱訪問靜態成員。無論創建多少個類實例,靜態成員只有一個副本。靜態方法和屬性無法訪問其包含類型的非靜態字段和事件,並且除非在方法參數中顯式傳遞了實例變量,否則它們無法訪問任何對象的實例變量。

這意味著靜態成員屬於類型本身,而不是對象。它們也由CLR加載到應用程序域中,因此靜態成員屬於承載應用程序的進程,而不是特定線程。

鑑於Web環境是多線程環境,因為每個請求都是由w3wp.exe進程產生的新線程;考慮到靜態成員是該過程的一部分,我們可能會遇到以下情況:幾個不同的線程嘗試訪問靜態(由多個線程共享的)變量的數據,這最終可能會導致多線程問題。

線程安全性下的Dictionary 文檔[8]聲明以下內容:

Dictionary<tkey>只要不修改集合,A 就可以同時支持多個閱讀器。即使這樣,通過集合進行枚舉本質上也不是線程安全的過程。在極少的枚舉與寫訪問競爭的情況下,必須在整個枚舉期間鎖定集合。要允許多個線程訪問該集合進行讀寫,您必須實現自己的同步。/<tkey>

此聲明解釋了為什麼我們可能會遇到此問題。根據轉儲信息,問題出在字典的FindEntry方法上:

如何在.NET应用程序中分析CPU使用率过高的问题

如果查看字典的FindEntry 實現,[9]我們可以看到該方法遍歷內部結構(存儲桶)以查找值。

因此,以下.NET代碼枚舉了集合,這不是線程安全的操作。

<code>publicoverrideTaskOnDisconnected {/<code><code>try/<code><code>{/<code><code>var userId = GetUserId;/<code><code>string connId;/<code><code>if(onlineSessions.TryGetValue(userId, out connId))/<code><code> onlineSessions.Remove(userId);/<code><code>}/<code><code>catch(Exception)/<code><code>{/<code><code>// ignored/<code><code>}/<code><code>returnbase.OnDisconnected;/<code><code>}/<code>

結論

正如我們在轉儲中看到的那樣,有多個線程試圖同時迭代和修改共享資源(靜態字典),最終導致迭代進入無限循環,從而導致線程消耗超過90%的CPU。。

有幾種可能的解決方案。我們首先實現的方法是鎖定和同步對字典的訪問,但會損失性能。那時服務器每天都崩潰,因此我們需要儘快解決此問題。即使這不是最佳解決方案,它也解決了該問題。

解決這個問題的下一步是分析代碼並找到最優解決方案。重構代碼是一個選項:新的ConcurrentDictionary類可以解決這個問題,因為它只鎖定在一個桶級別,這將提高整體性能。儘管這是一大步,還需要進一步的分析。

References

<code>[1]/<code>.NET開發人員:

https://www.toptal.com/dot-net

<code>[2]/<code>常見錯誤:https://www.toptal.com/c-sharp/top-10-mistakes-that-c-sharp-programmers-make

<code>[3]/<code>Performance Monitor:https://technet.microsoft.com/en-us/library/cc749115.aspx

<code>[4]/<code>New Relic Servers:http://newrelic.com/server-monitoring

<code>[5]/<code>Debug Diagnostic Tools v2.0:https://www.microsoft.com/en-us/download/details.aspx?id=49924

<code>[6]/<code>static:https://msdn.microsoft.com/en-us/library/98f28cdx.aspx

<code>[7]/<code>靜態類和成員的說明:https://msdn.microsoft.com/en-us/library/79b3xss3.aspx

<code>[8]/<code>文檔:https://msdn.microsoft.com/en-us/library/xfhwa508%28v=vs.100%29.aspx

<code>[9]/<code>實現,:http://referencesource.microsoft.com/#mscorlib/system/collections/generic/dictionary.cs,bcd13bb775d408f1


分享到:


相關文章: