.NET應(yīng)用程序七種常見的性能問(wèn)題及其解決方案
Microsoft .NET Framework是最流行的應(yīng)用程序開發(fā)平臺(tái)和編程語(yǔ)言之一。C#和ASP.NET框架已被數(shù)百萬(wàn)開發(fā)人員用于構(gòu)建Windows客戶端應(yīng)用程序,XML Web服務(wù),分布式組件,客戶端-服務(wù)器應(yīng)用程序,數(shù)據(jù)庫(kù)應(yīng)用程序等。
隨著.NET應(yīng)用范圍越來(lái)越廣泛,對(duì)于大多數(shù)應(yīng)用程序所有者和開發(fā)人員來(lái)說(shuō),確保.NET應(yīng)用程序的優(yōu)良性能是最重要的需求。
.NET應(yīng)用程序運(yùn)行緩慢的原因可能有很多。這些包括不正確的內(nèi)存大小調(diào)整,GC暫停,代碼級(jí)錯(cuò)誤,異常的過(guò)多日志記錄,同步塊的高使用率,IIS服務(wù)器瓶頸等等。
在此博客中,我們將研究.NET應(yīng)用程序中的一些常見性能問(wèn)題,并提供解決和解決這些問(wèn)題的技巧。
1.未處理的異常與記錄過(guò)多異常日志
.NET異常不是一件壞事,只有錯(cuò)誤的用法卻是壞事。這就是大多數(shù)開發(fā)人員所相信的,如果對(duì)異常進(jìn)行了適當(dāng)?shù)奶幚?,即拋出,捕獲和處理(并且不忽略),則會(huì)帶來(lái)穩(wěn)定的性能。
然而,就像太多的廚師會(huì)寵壞了湯一般,太多未處理的異常會(huì)導(dǎo)致代碼效率低下并影響應(yīng)用程序性能[1]。
尤其是隱藏的異常更糟,這種隱藏異常就像雷區(qū),一旦未能檢查這些異常,它們會(huì)影響網(wǎng)頁(yè)加載時(shí)間。
.NET的另一個(gè)問(wèn)題是過(guò)多地記錄了異常。日志記錄可能是您的調(diào)試工具庫(kù)中的一個(gè)好工具,它可以識(shí)別在處理應(yīng)用程序時(shí)記錄的異常。
但是,當(dāng)設(shè)置日志記錄來(lái)捕獲應(yīng)用程序體系結(jié)構(gòu)每一層的異常時(shí),最終可能會(huì)在Web,服務(wù)和數(shù)據(jù)層上記錄相同的異常。這可能會(huì)增加應(yīng)用程序代碼的負(fù)擔(dān),并增加響應(yīng)時(shí)間。在生產(chǎn)環(huán)境中,只需要記錄致命事件和錯(cuò)誤就需要小心。
記錄所有信息,包括參考消息,調(diào)試和警告,很容易使您的生產(chǎn)日志文件file臃腫,進(jìn)而影響代碼處理。
有用的疑難解答提示:
- 確保您的C#代碼具有“嘗試最終捕獲”塊以處理異常。
- 利用C#6和更高版本中提供的異常過(guò)濾器,該過(guò)濾器允許為每個(gè)catch塊指定一個(gè)條件子句
- 檢查空值,并使用TryParse避免潛在的異常。
- 請(qǐng)注意第二次機(jī)會(huì)異常,因?yàn)樗鼈儽砻鞒霈F(xiàn)了第一次機(jī)會(huì)異常,并且未正確處理它。
- 使用異常處理和日志記錄庫(kù)(例如企業(yè)庫(kù),NLog,Serilog或log4net)將異常記錄到文件或數(shù)據(jù)庫(kù)中。
- 確保僅根據(jù)需要記錄異常,并且最終不會(huì)使日志文件膨脹。
2.過(guò)度使用線程同步和Lock
.NET Framework提供了許多線程同步選項(xiàng),例如進(jìn)程間互斥,讀取器/寫入器鎖等。
有時(shí),.NET開發(fā)人員將以這樣的方式編寫代碼:在給定的條件下,只有一個(gè)線程可以得到服務(wù)。
時(shí)間以及其他要處理的并行線程將不得不在隊(duì)列中等待。例如,結(jié)帳應(yīng)用程序根據(jù)其業(yè)務(wù)邏輯應(yīng)一次處理一個(gè)請(qǐng)求。同步和鎖定有助于序列化傳入線程以執(zhí)行。通過(guò)創(chuàng)建同步的代碼塊并在特定對(duì)象上施加鎖定,需要傳入線程等待直到同步對(duì)象上的鎖可用為止。盡管此策略在某些情況下會(huì)有所幫助,但不應(yīng)過(guò)度使用。
有用的疑難解答提示:
- 使用同步編碼,僅在必要時(shí)使用鎖。在決定使用鎖之前,請(qǐng)先了解代碼執(zhí)行的需求。
- 最佳地調(diào)整鎖的持續(xù)時(shí)間范圍,以便延遲獲取并提前釋放它們,并且不會(huì)長(zhǎng)時(shí)間等待其他線程。
- 為了減少并發(fā)問(wèn)題,請(qǐng)考慮使用松散耦合。事件委托模型也可以用于最小化鎖爭(zhēng)用。
- 使用代碼分析工具[2]監(jiān)視.NET代碼,以識(shí)別線程鎖定是否導(dǎo)致應(yīng)用程序處理緩慢。
3.應(yīng)用程序掛起
有時(shí)特定的URL緩慢時(shí),這是一回事。但是,當(dāng)IIS網(wǎng)站剛剛掛起并且所有或大多數(shù)網(wǎng)頁(yè)需要永久加載時(shí),它不會(huì)變得更糟。通常,當(dāng)應(yīng)用程序過(guò)載或死鎖時(shí),可能會(huì)掛起。.NET應(yīng)用程序通常會(huì)遇到兩種類型的應(yīng)用程序掛起方案。
硬掛(IIS問(wèn)題):這通常發(fā)生在請(qǐng)求處理管道的開始–在請(qǐng)求排隊(duì)的地方。由于應(yīng)用程序死鎖,所有可用線程都可能被阻塞,導(dǎo)致隨后的傳入請(qǐng)求最終在等待服務(wù)的隊(duì)列中結(jié)束。當(dāng)活動(dòng)請(qǐng)求數(shù)超過(guò)IIS服務(wù)器上配置的并發(fā)限制時(shí),也會(huì)發(fā)生這種情況。此類掛起將表現(xiàn)為請(qǐng)求超時(shí)并收到503 Service Unavailable錯(cuò)誤。硬性影響所有URL和整個(gè)Web應(yīng)用程序本身。
有用的疑難解答提示:
- 不斷跟蹤IIS服務(wù)器中隊(duì)列中的請(qǐng)求數(shù)(Windows性能監(jiān)視器中為Http Service Request Queues \ ArrivalRate)。這決不能超過(guò)為工作進(jìn)程配置的請(qǐng)求處理限制。
- 還跟蹤隊(duì)列中的請(qǐng)求等待時(shí)間(Windows性能監(jiān)視器中的Http Service Request Queues \ MaxQueueItemAge)。這將有助于檢測(cè)應(yīng)用程序是否面臨潛在的掛起。
- 還可以通過(guò)監(jiān)視IIS服務(wù)器事件來(lái)注意服務(wù)不可用和連接超時(shí)錯(cuò)誤。
軟掛(ASP.NET問(wèn)題):這通常是由于特定段中的應(yīng)用程序代碼錯(cuò)誤而造成的,僅影響幾個(gè)URL而不影響整個(gè)網(wǎng)站。通常,由ASP.NET控制器或頁(yè)面引起的掛起發(fā)生在
ExecuteRequestHandler
階段。為了確認(rèn)這一點(diǎn),您可能想調(diào)試一下調(diào)試器,以確切了解請(qǐng)求被卡在哪里。檢查模塊名稱,階段名稱和URL。URL將指示導(dǎo)致掛起的控制器/頁(yè)面。
有用的疑難解答提示:
- 通過(guò)檢查Windows性能監(jiān)視器中的Http服務(wù)請(qǐng)求隊(duì)列\(zhòng) CurrentQueueSize計(jì)數(shù)器,驗(yàn)證IIS是否是問(wèn)題。如果為0,則IIS隊(duì)列中沒(méi)有任何請(qǐng)求。
- 如果不是IIS問(wèn)題,則必須是ASP.NET控制器/頁(yè)面中的代碼級(jí)問(wèn)題。
- 使用任何代碼分析,確定哪些URL掛起并獲得詳細(xì)的請(qǐng)求跟蹤。驗(yàn)證請(qǐng)求掛起的模塊名稱和階段名稱,以確認(rèn)這是ASP.NET問(wèn)題。
- 使用事務(wù)跟蹤工具[3]進(jìn)行代碼分析可以幫助識(shí)別存在問(wèn)題的確切代碼行。
4.頻繁垃圾回收暫停
當(dāng)托管堆上分配的對(duì)象使用的內(nèi)存超過(guò)應(yīng)用程序開發(fā)人員配置的可接受閾值時(shí),.NET CLR中的垃圾回收(GC)會(huì)初始化。這是GC.Collect方法跳轉(zhuǎn)到動(dòng)作并回收死對(duì)象占用的內(nèi)存的時(shí)候。CLR中的GC通常發(fā)生在存儲(chǔ)短期對(duì)象的第0代堆中。當(dāng)GC發(fā)生在包含長(zhǎng)期對(duì)象的第二代堆中時(shí),它稱為Full GC。每次發(fā)生GC都會(huì)在CLR上增加大量CPU負(fù)載,并減慢應(yīng)用程序的處理速度。因此,如果GC暫停時(shí)間更長(zhǎng)且更頻繁,則應(yīng)用程序?qū)②呌诜啪彙?/p>
有用的疑難解答提示:
- 適當(dāng)調(diào)整GC堆內(nèi)存的大小,并確保已根據(jù)需要設(shè)置GC限制。
- 避免在不需要它們的地方使用對(duì)象和大字符串。
- 跟蹤GC的實(shí)例,GC花費(fèi)的時(shí)間以及JVM花費(fèi)的GC時(shí)間的百分比。
- 尋找發(fā)生完全GC的時(shí)間。這可能導(dǎo)致應(yīng)用程序變慢。
- 根據(jù)應(yīng)用程序需求明智地使用服務(wù)器GC或工作站GC。
- 端到端監(jiān)視CLR層以識(shí)別內(nèi)存使用情況,GC活動(dòng),CPU峰值等。
5.IIS服務(wù)器瓶頸
Microsoft IIS Server是.NET Framework的關(guān)鍵部分。IIS是Web服務(wù)器,它承載構(gòu)建于.NET上的Web應(yīng)用程序或網(wǎng)站,并運(yùn)行W3WP進(jìn)程,該進(jìn)程負(fù)責(zé)響應(yīng)傳入的請(qǐng)求。IIS還集成了公共語(yǔ)言運(yùn)行時(shí)(CLR),該運(yùn)行時(shí)負(fù)責(zé)為線程處理分配資源。由于IIS具有各種活動(dòng)部分,因此IIS中的瓶頸可能會(huì)對(duì).NET應(yīng)用程序性能產(chǎn)生直接的負(fù)面影響。
常見的IIS服務(wù)器問(wèn)題:
- 由于內(nèi)存,CPU等資源的過(guò)度利用而導(dǎo)致服務(wù)器超載
- 高并發(fā)連接數(shù)和連接數(shù)下降
- 應(yīng)用程序池故障
- SSL證書已過(guò)期
- ASP.NET請(qǐng)求處理服務(wù)的高響應(yīng)時(shí)間
- 高CLR等待時(shí)間
- 緩存不正確
- HTTP錯(cuò)誤,包括靜態(tài)和動(dòng)態(tài)內(nèi)容錯(cuò)誤以及連接錯(cuò)誤
有用的疑難解答提示:
- 調(diào)整IIS服務(wù)器的大小,以便不存在資源爭(zhēng)用或資源過(guò)度利用的情況。
- 根據(jù)傳入請(qǐng)求的速率與更多IIS服務(wù)器進(jìn)行負(fù)載平衡。
- 跟蹤SSL證書的有效性,并在證書過(guò)期之前主動(dòng)發(fā)出警報(bào)。
- 監(jiān)視IIS性能[4],應(yīng)用程序池,網(wǎng)站的所有方面,[5]并識(shí)別不正確的配置和性能偏差。
6.數(shù)據(jù)庫(kù)慢查詢
并非總是會(huì)影響應(yīng)用程序性能的.NET代碼問(wèn)題。運(yùn)行緩慢的查詢通常是常見的原因。但是通常是.NET應(yīng)用程序開發(fā)人員因應(yīng)用程序性能下降[6]而受到指責(zé)。
這樣做的原因是,SQL性能如何影響.NET應(yīng)用程序處理沒(méi)有上下文可見性。ADO.NET和ODP.NET連接問(wèn)題可能是查詢處理緩慢的原因之一,但常見原因是查詢的格式不正確。執(zhí)行計(jì)劃不正確,索引缺失,架構(gòu)設(shè)計(jì)不當(dāng),緩沖池較小,聯(lián)接缺失,緩存不正確,連接未正確進(jìn)行池化等也是導(dǎo)致數(shù)據(jù)庫(kù)查詢處理受到影響的原因。
雖然DBA負(fù)責(zé)數(shù)據(jù)庫(kù)性能和查詢創(chuàng)建,但.NET應(yīng)用程序所有者需要在應(yīng)用程序處理期間跟蹤查詢級(jí)別的問(wèn)題。這將有助于區(qū)分代碼級(jí)問(wèn)題和數(shù)據(jù)庫(kù)問(wèn)題,并且.NET開發(fā)人員不必花時(shí)間尋找代碼中的問(wèn)題。
有用的疑難解答提示:
- 在應(yīng)用程序事務(wù)的上下文中監(jiān)視查詢處理以識(shí)別慢查詢。
- 適當(dāng)規(guī)劃數(shù)據(jù)庫(kù)的大小和配置,以確保一致的性能。
- 使用數(shù)據(jù)庫(kù)監(jiān)視工具[7]來(lái)識(shí)別和修復(fù)丟失的索引,通過(guò)重新索引來(lái)優(yōu)化數(shù)據(jù)庫(kù)布局等。
- 跟蹤數(shù)據(jù)庫(kù)與應(yīng)用程序的連接,以隔離所有連接問(wèn)題。
溫馨提示:除了數(shù)據(jù)庫(kù)調(diào)用速度慢之外,由于外部調(diào)用[8](例如HTTP,Web Service,WCF)也可能導(dǎo)致速度慢[9]。
7.基礎(chǔ)設(shè)施故障:盡管不是.NET框架的問(wèn)題,但仍是一個(gè).NET問(wèn)題!
.NET Framework不是獨(dú)立的層。使用.NET Framework的應(yīng)用程序?qū)⑴c基礎(chǔ)架構(gòu)(例如任何虛擬服務(wù)器,容器或云基礎(chǔ)架構(gòu))有很多依賴性。然后,可能會(huì)有后端存儲(chǔ)設(shè)備。盡管這些不是直接的.NET問(wèn)題,但是這些基礎(chǔ)結(jié)構(gòu)組件中的任何一個(gè)問(wèn)題都可能同樣影響.NET性能。
就像我們看到IIS服務(wù)器和數(shù)據(jù)庫(kù)可能遇到瓶頸一樣,VM可能會(huì)耗盡資源,SAN陣列可能會(huì)遇到無(wú)法處理的高IOPS,或者如果.NET應(yīng)用程序托管在Azure上,那么可能會(huì)有一個(gè)應(yīng)用程序服務(wù)運(yùn)行不正常。
在大多數(shù)應(yīng)用程序環(huán)境中,與網(wǎng)絡(luò)相關(guān)的投訴都位居榜首。無(wú)論是網(wǎng)絡(luò)問(wèn)題還是應(yīng)用程序問(wèn)題之間總是存在責(zé)備游戲。網(wǎng)絡(luò)擁塞,丟包或設(shè)備故障可能會(huì)影響應(yīng)用程序的性能和連接性。
有用的疑難解答提示:
.NET應(yīng)用程序環(huán)境的總體性能保證要求應(yīng)用程序與支持基礎(chǔ)結(jié)構(gòu)之間的依存關(guān)系具有相關(guān)的可見性。確保實(shí)施融合的應(yīng)用程序和基礎(chǔ)結(jié)構(gòu)監(jiān)視策略以捕獲基礎(chǔ)結(jié)構(gòu)問(wèn)題。
當(dāng)您專注于捕獲和解決所有這些問(wèn)題時(shí),請(qǐng)務(wù)必記住,編寫干凈而高效的代碼可以解決.NET方面的許多問(wèn)題。編寫良好的代碼,保持系統(tǒng)和基礎(chǔ)架構(gòu)的健康,并實(shí)施必要的工具以監(jiān)控自動(dòng)化。這將幫助您提供高性能的.NET應(yīng)用程序和數(shù)字體驗(yàn)。
References
[1] 應(yīng)用程序性能: https://www.eginnovations.com/blog/what-is-application-performance-monitoring/
[2] 代碼分析工具: https://www.eginnovations.com/microsoft-net-monitoring
[3] 事務(wù)跟蹤工具: https://www.eginnovations.com/microsoft-net-monitoring
[4] 監(jiān)視IIS性能: https://www.eginnovations.com/iis-monitoring
[5] 所有方面,: https://www.eginnovations.com/iis-monitoring
[6] 應(yīng)用程序性能下降: https://www.eginnovations.com/webinar/my-application-is-slow-troubleshooting-prevention/
[7] 數(shù)據(jù)庫(kù)監(jiān)視工具: https://www.eginnovations.com/database-monitoring
[8] 速度慢之外,由于外部調(diào)用: https://www.eginnovations.com/microsoft-net-monitoring#supported
[9] 速度慢: https://www.eginnovations.com/microsoft-net-monitoring#supported