编写高性能 Web 应用程序的 10 个技巧 (转)
生活随笔
收集整理的這篇文章主要介紹了
编写高性能 Web 应用程序的 10 个技巧 (转)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
http://www.microsoft.com/china/msdn/library/webservices/asp.net/us0501ASPNETPerformance.mspx
本文討論:
常見的?ASP.NET?性能神話?
有用的?ASP.NET?性能技巧和訣竅?
在?ASP.NET?中處理數據庫的一些建議?
緩沖以及用?ASP.NET?進行后臺處理?
本文使用下列技術:ASP.NET,.NET?框架,IIS
用?ASP.NET?編寫?Web?應用程序其輕松程度令人難以置信。它是如此的容易,以至于許多開發人員不用花費多少時間來構筑其應用便能獲得非常好的性能。在本文中,我將給出10個編寫高性能?Web?應用的技巧。我的評論不僅僅局限與?ASP.NET?應用,因為它們只是?Web?應用的一個子集。本文也不是?Web?應用性能調整的權威指南——這方面的內容可以寫成一本書。相反,本文可以被視作一個好的起點。
在廢寢忘食地工作之前,我常常要去攀巖。在攀巖之前,我總是要看一下指南手冊中的線路并閱讀以前來此一游的人留下的建議和忠告。但是,不管指南手冊有多磨好,在嘗試一次特定的具有挑戰性的攀爬之前,你都必須付諸實際的行動。同樣,在你面臨解決的性能問題或者營運一個高吞吐量的站點之前,你只能想方設法編寫高性能?Web?應用程序。
我們個人經驗來自在微軟?ASP.NET?團隊從事底層架構程序經理,運行和管理?www.asp.net?,并協助架構?Community?Server?過程中的經歷,Community?Server?是幾個有名的?ASP.NET?應用程序的下一個版本(它將?ASP.NET?Forums,.Text?和?nGallery?整合到一個平臺)。我確信這些幫助過我的技巧也會對你有所裨益。
你應該考慮將應用程序分離成幾個邏輯層。你可能聽說過術語3-層(或n-層)物理體系結構。它們通常是跨進程和/或硬件對功能進行物理劃分的規定的體系結構模式。當系統需要伸縮時,更多的硬件能被添加。然而,總是應該避免與進程和機器忙碌程度相關的性能問題。所以,不管什么時候,只要可能,都要在相同的應用中一起運行?ASP.NET?頁面及其相關的組件。
由于代碼和層之間的邊界分離,使用?Web?服務或遠程調用將降低20%以上的性能。
數據層則稍微有些不同,因為數據庫通常都用專門的硬件。但是,數據庫的處理成本仍然很高,因此最優化代碼時,數據層的性能應該是首當其充要關注的地方。
在著手解決你的應用程序的性能問題之前,一定要剖析應用程序,確定問題之所在。獲取關鍵的性能計數器值(如實現垃圾收集所花時間之百分比的性能計數器的值)對于查找應用程序在何處最耗時也是非常重要的。憑借直覺常常也能找到耗時所在。
本文所描述的性能改進有兩種類型:大型優化,如使用?ASP.NET?Cache,以及不斷重復進行的微型優化。這些微型優化有時很有意思。對代碼的小小改動便會引起很大的動靜,產生成千次的調用。對于大型優化,你可能會看到整體性能的大跳躍。而對微型優化,給定請求可能只是毫秒級的調整,但按每天的請求總數計算,其結果的改進可能是巨大的。
數據層的性能
當調整某個應用程序的性能時,有一個簡單的試金石,你可以用它按先后次序:檢查代碼是否存取數據庫?如果是,多長時間存取一次?注意相同的測試也可以被應用于使用?Web?服務或遠程調用的代碼,但我們本文中不涉及這方面內容。
如果在特定的代碼流程中必須具有對數據庫的請求以及要考察其它方面,如:想對字符串處理進行優先優化,那么暫且把它放一放,先按照上面定好的優先次序來做。除非你有異乎尋常的性能問題,否則你的時間應該用在嘗試最優化與數據庫的連接所花的時間,返回的數據量以及多長時間往返一次和數據庫的通訊上。
有了這些概括信息,下面就讓我們來看看能幫助你改善應用程序性能的十個技巧。我將從能獲得最顯著效果的改變開始。
技巧?1?——?返回多個結果集
復審你的數據庫代碼,看看是否有多于一次的對數據庫的訪問請求。這樣每次往返數據庫都降低你的應用程序能處理的每秒請求數。通過在單個數據庫請求中返回多結果集,你能降低與數據庫通信的總體時間。同時你也將使系統更具伸縮性,因為你減少了數據庫服務器處理請求的負擔。
雖然你可以用動態?SQL?返回多結果集,我更喜歡使用存儲過過程。是否將業務邏輯駐留在存儲過程當中是個有待爭論的問題,但我認為,如果存儲過程中的邏輯能約束返回的數據(降低數據集的尺寸,在網絡上傳輸的時間以及邏輯層不必過慮數據),這是一件好事情。
使用?SqlCommand?命令實例及其?ExecuteReader?方法來處理強類型的各個業務類,你通過調用?NextResult?可以向前移動結果集指針。Figure?1?示范了處理幾個帶類型的?ArrayLists?例子會話。從數據庫只返回你需要的數據還會降低服務器上內存的分配。
技巧?2?——?分頁數據存取
ASP.NET?DataGrid?提供了非常好的能力:數據分頁支持。當啟用?DataGrid?中的分頁功能,則每次只顯示固定數量的記錄。此外,分頁用戶界面也會顯示在?DataGrid?底部用于導航記錄。分頁用戶界面允許你向前向后導航所顯示的記錄,一次顯示固定數量的記錄。
有一個美中不足的是用?DataGrid?分頁需要將所有數據邦定到此柵格控件(gird)。例如,你的數據層必須返回所有數據,然后?DataGrid?將根據當前頁過濾掉所有顯示的記錄。當你通過?DataGrid?進行分頁時,如果有?100,000?條記錄被返回,那么每個請求有?99,975?條記錄將被廢棄掉(假設頁尺寸為?25)。當記錄數不斷增加,此應用程序的性能便會遭受痛苦,因為每次請求所要發送的數據會越來越多。
編寫較好的分頁代碼的一個好的方法是用存儲過程。Figure?2?示范了一個用?Northwind?數據庫中?Orders?表通過存儲過程分頁的例子。很簡單,只要你在頁面中傳遞索引以及頁尺寸即可。相應的結果集先被計算然后被返回。
在?Community?Server?中,我們編寫了幾個分頁控件來完成數據分頁。你將會看到,我使用了技巧?1?中討論的思路,從一個存儲過程中返回連個結果集:總記錄數和請求的數據。
返回的總記錄數依賴于所執行的查詢不同而不同。例如,某個?WHERE?子句可被用于約束返回的數據。為了計算在分頁用戶界面顯示的總頁數,返回的總記錄數必須是已知的。例如,如果有?1,000,000?條記錄,用一個?WHERE?子句對之過濾后為?1,000?條記錄,則分頁邏輯必須要知道總記錄數以便在分頁用戶界面中正確呈現。
技巧?3?——?連接池
建立?Web?應用程序與?SQL?Server?之間的?TCP?連接是一項昂貴的操作。微軟的開發人員利用連接池技術已經有好長一段時間了,這個技術使他們能重用到數據庫的連接。而不是每次請求都建立新的?TCP?連接,新連接僅在連接池中得不到連接時才建立。當連接被關閉時,它被返回到連接池中,在那里它仍然保持與數據庫的連接,與完全斷開?TCP?連接相反。
當然,你需要提防泄漏的連接。當你處理完畢,一定要關閉連接。重申一次:不管人們怎么吹噓微軟?.NET?框架中的垃圾收集特性,每當你處理完畢,一定要顯式地調用連接對象的?Close?或?Dispose?方法。不要指望公共語言運行時(CLR)來為你定時清除和關閉連接。CLR?最終將銷毀類并強行關閉連接,但你無法保證該對象的垃圾收集屆時會起作用。
為了充分用好連接池,有幾條規則必須了然于心。首先,打開連接,進行處理,然后關閉連接。寧愿每個請求的連接打開和關閉多次,也不要保持連接打開狀態以及在不同的方法間將它傳來傳去。其次,使用相同的連接串(如果你使用集成身份檢查,那么也要用相同的線程身份)。如果你不用相同的連接串,例如,根據登錄用戶來定制連接串,你將無法得到連接池所提供的相同的最優化值。當模擬大用戶量情形時,如果你使用集成身份檢查,那么你的連接池將效力大減。.NET?CLR?數據性能計數器在試圖跟蹤任何與連接池有關的性能問題時是非常有用的。
不管什么時候,只要你的應用程序連接到運行在其它進程中的資源,比如某個數據庫,你都應該針對連接到資源所耗時間,發送和接收數據所耗時間以及往返次數進行優化。為了實現較好的性能,應該首當其充優化應用程序中任何種類的忙碌進程。
應用層包含到數據層的連接以及將數據轉換成有意義的類實例和業務處理的邏輯。以?Community?Server?為例,你要在其中處理?Forums?和?Threads?集合;以及應用許可這樣的業務規則;尤其重要的是緩沖(Caching)邏輯也實現其中。
技巧?4?——?ASP.NET?Cache?API
在編寫代碼之前要做的頭等大事之一是最大限度地構建應用層并發掘?ASP.NET?的?Cache?特性。
如果你的組件在?ASP.NET?應用程序內運行,那么你只需要在應用程序工程中引用?System.Web.dll?即可。當你需要訪問?Cache?時,用?HttpRuntime.Cache?屬性(相同的對象也可以通過?Page.Cache?和?HttpContext.Cache?訪問)。
緩沖數據有幾個準則。首先,如果數據能被使用多次,緩沖是個好的后選方案。其次,如果數據對給定請求或用戶是一般的數據而非專用數據,那么最好是選擇緩沖。如果數據用戶或請求專用,如果需要保存期很長但可能不被經常使用,那么仍然要用緩沖。第三,常常被忽略的一個準則是有時緩沖太多的東西。一般來說,在x86機器上,為了降低內存不足錯誤的幾率,運行某個進程不要超過800MB私有字節。因此,緩沖應該有個上限。換句話說,你也許能重用某個計算的結果,但如果該計算有10個參數,你可能試圖針對10個置換進行緩沖,這樣做可能會給你帶來麻煩。ASP.NET?提供的最常見的容錯是由覆蓋緩沖導致的內存不足錯誤,尤其是大型數據集。
Cache?有幾個重要特性是必須要了解的。第一個是?Cache?實現了最近最少使用(least-recently-used)算法,允許?ASP.NET?強制?Cache?清除操作?——?如果可用內存下降到低水平?——?則自動從?Cache?中刪除不使用的項目。第二個是?Cache?支持依賴性到期特性,它能強制包括時間,鍵值,文件失效。時間常常被使用,但?ASP.NET?2.0?引入了具有更強大的失效類型:數據庫緩沖失效。也就是當數據庫中的數據改變時,緩沖中的條目會自動刪除。有關數據庫緩沖失效的更多信息參見?Dino?Esposito?在?MSDN?雜志?2004?年七月刊的?Cutting?Edge?專欄文章。該緩沖的體系結構,參見?Figure?3。
Figure?3?ASP.NET?Cache
技巧?5?——?預請求緩沖(Per-Request?Caching)
在本文前面,我曾提到對頻繁執行的代碼塊所做的小小改動可能產生很大的,整體性能的提升。我把其中一個我特別中意的叫做預請求緩沖(per-request?caching)。
由于?Cache?API??被設計用來緩沖長期數據或直到某個條件被滿足,預請求緩沖意旨用于請求期間的緩沖該數據。特定的代碼流程被每次請求頻繁訪問但是數據只需要被拾取,應用,修改或更新一次,這樣說太理論化,還是讓我們看一個具體的例子吧。
在?Community?Server?的?Forums?(論壇)應用中,某個頁面上使用的每個服務器控件需要個性化數據以確定使用那個皮膚和式樣頁,以及其它的個性化數據,其中有些數據可以被長時間緩沖,但有些數據,比如用于控件的皮膚在單個請求中只被拾取一次并在該請求執行期間被重用多次。
為了完成預請求緩沖,用?ASP.NET?HttpContext。HttpContext?的實例是隨每個請求創建的,并可以通過?HttpContext.Current?屬性在那個請求執行期間的任何地方存取它。HttpContext?類具有一個特別的?Items??集合屬性,被添加到該?Items??集合的對象和數據只是在該請求期間被緩存。就像你可以使用?Cache?來保存頻繁使用的數據一樣,你可以用?HttpContext.Items?來保存只在某個預請求中使用的數據。在此背景后的邏輯很簡單:當數據不存在時被添加到?HttpContext.Items?集合,以及在隨后的并發查找中簡單地返回?HttpContext.Items?中發現的數據。
技巧??6——后臺處理
你的代碼流程應該盡可能快,對吧?你自己可能多次發現要完成每個請求或每n個請求的任務代價很高。發出?e-mail?或解析并檢查輸入數據的有效性就是個例。
在重新生成?ASP.NET?Forums?1.0?并把它整合到?Community?Server?時,我們發現添加新貼的代碼流程非常慢。每次添加帖子,應用程序首先要確保沒有重復貼,然后必須用“badword”過濾器解析該貼的表情圖像,記號并索引,如果必要還要將帖子添加到相應的隊列中,對附件進行有效性檢查,最終完成發貼后,給預訂者發出?e-mail?通知。顯然,這里做的工作太多。
我們發現大多數時間都花在了索引邏輯和發送e-mail上。索引帖子是一個很耗時的操作,此外,內建的?System.Web.Mail?功能要與?SMTP?服務器連接并順序發送郵件。當特定帖子或主題預定者數量增加時,AddPost?函數的執行時間會越來越長。
并不是每個請求都需要索引郵件,我們想最好是批量集中處理,并且一次只索引25個帖子或每隔五分鐘發送一次郵件。我們決定使用的代碼與我曾在原型數據庫緩沖失效中所使用的代碼相同,最終它也被納入?Visual?Studio?2005。
名字空間?System.Threading?中的?Timer?類非常有用,但在.NET?框架中鮮為人知,至少對?Web?開發者來說是這樣。一旦創建,Timer?將以可定制的間隔針對線程池中的某個線程調用指定的回調函數。這意味著你不用輸入請求到?ASP.NET?應用程序便能讓代碼實行,這是一種最合適后臺處理的情形。你也可以在這種后臺處理模式中進行例如索引或發送電子郵件這樣的工作。
盡管如此,這個技術存在幾個問題,如果你的應用程序域關閉,該定時器實例將停止觸發其事件。另外,由于?CLR?有一個硬坎,即每個進程的線程數是固定的,你便可能陷入嚴重的服務器負荷當中,此時可能就沒有線程來處理定時器,從而造成延時。為了讓發生這種情況的幾率最小化,ASP.NET?通過在進程中預留一定數量的空閑線程,并只使用部分線程來處理請求。然而,如果你有許多異步處理,這樣做會有問題。
由于篇幅所限,在此無法列出代碼,但你可以從?www.rob-howard.net?下載可消化的例子。其中有?Blackbelt?TechEd?2004?展示的幻燈和?Demo。
技巧?7——頁面輸出緩存和代理服務器
ASP.NET?是你的表示層(或者說應該是);它由頁面,用戶控件,服務器控件(HttpHandlers?and?HttpModules)以及它們生成的內容組成。如果你有一個產生輸出的?ASP.NET?頁面,不管是輸出?HTML,XML,圖像還是任何其它數據,而且每個請求你都運行這個代碼并產生相同的輸出,此時最好選擇使用頁面輸出緩存。
只要在頁面頂部添加這一行代碼即可:
<%@?Page?OutputCache?VaryByParams="none"?Duration="60"?%>?
你可以為此頁面有效地產生一次輸出并可以在60秒內多次重用它,一到這個時間點,該頁面將重新執行并將再次將輸出添加到?ASP.NET?Cache。這個行為還能用某些低級編程?APIs?來完成。輸出緩存有幾個可以配置的設置,比如:VaryByParams?屬性。VaryByParams?不是必須的,但允許你指定?HTTP?GET?或?HTTP?POST?參數來改變緩存入口。例如,default.aspx?Report=1?或?default.aspx?Report=2?可以簡單地設置?VaryByParam="Report"?來對輸出進行緩存。額外的參數被命名并用用分號分隔。
在使用輸出緩存機制時,許多人都不了解?ASP.NET?頁還產生一組下游緩存服務器?HTTP?頭,比如?Microsoft?Internet?Security?and?Acceleration?Server?或?Akamai?使用的?HTTP?頭。當設置?HTTP?緩存頭,文檔可以被緩存到這些網絡資源,從而響應客戶端請求不必返回原服務器。
然而,使用頁面輸出緩存并不會使你的應用程序更有效率,但它能通過下游緩存技術緩存文檔從而潛在地降低服務器的負載。當然,這只能是異步內容;一旦實施下游緩存,你將無法看到任何請求,也不能實現身份認證來防止對它的存取。
技巧?8——運行?IIS?6.0?(如果僅用于內核緩存)
如果你不運行?IIS?6.O(Windows?Server?2003),那么你將得不到微軟?Web?服務器中一些重大的性能改進。在技巧?7?中,我談到了輸出緩存。在?IIS?5.0?中,請求到達?IIS,然后到達?ASP.NET。當使用緩存時,ASP.NET?中的?HttpModule?接受該請求,并從該緩存中返回內容。
如果你用?IIS?6.0,有一些巧妙的特性叫內核緩存,它不需要將任何代碼改成?ASP.NET。當?ASP.NET對請求進行緩存處理,IIS?內核緩存便接收一份緩存數據的拷貝。當請求來自網絡驅動器,內核一級的驅動程序(沒有到用戶模式的上下文轉換)接收該請求,如果緩存,則直接用緩存數據響應并完成執行。這意味著當你使用?IIS?內核模式緩存和?ASP.NET?緩存時,你將看到無法置信的性能結果。在開發?Visual?Studio?2005?的?ASP.NET?期間,我是負責?ASP.NET?性能的程序經理。開發人員的工作做的真是棒極了,而我基本上每天都在看報告。內核模式緩存結果總是最有趣的。典型的情況是請求/響應往往使網絡飽和,但?IIS?的運行僅占?CPU?的百分之五。真令人驚異!當然使用?IIS?6.O?有其它一些原因,但內核模式緩存是顯而易見的理由。
技巧?9——使用?Gzip?壓縮
雖然使用?gzip?壓縮不是一個必須的服務器性能技巧(因為你可能看到?CUP?的使用率上升了),但它能降低服務器發送字節的數量。從而感覺頁面更快,而且減少帶寬的占用。其壓縮的效果好壞取決于所發送的數據以及客戶端瀏覽器是否支持這種壓縮(IIS?只會將數據發送到支持?gzip?的瀏覽器,比如:IE?6.0?和?Firefox),從而使服務器可以在每秒鐘里處理更多的請求。事實上,只要你降低返回數據的數量,便能提高每秒所處理的請求數。
有一個好消息是?gzip?壓縮是?IIS?6.0?的內建特性,并且比它在?IIS?5.0?中使用的效果更好。但是,要想在?IIS?6.0?中啟用?gzip?壓縮可能沒那么方便,IIS?的屬性對話框里找不到設置它的地方。IIS?團隊將卓越的?gzip?壓縮能力內建在服務器中,但忽視了建立一個啟用壓縮特性的管理用戶界面。要想啟用?gzip?壓縮機制,你必須深入到?IIS?的?XML?配置設置內部(必須對之相當熟悉才能配置)。順便提一下,在此感謝?OrcsWeb?的?Scott?Forsyth?幫我解決了在?OrcsWeb?數個?www.asp.net?服務器上的這個問題。
與其在本文中包含整個過程,還不如閱讀?Brad?Wilson?在?IIS6?Compression?上的文章。微軟知識庫也有一篇關于為ASPX啟用壓縮特性的文章:Enable?ASPX?Compression?in?IIS。但是,還必須注意一點,動態壓縮與內核緩存由于某些實現細節的原因,其在?IIS?6.0?中是相互排斥的。
技巧?10——服務器控件的可視狀態
可視狀態(View?State)對于?ASP.NET?來說是個奇特的名字,它在所產生的頁面中隱藏輸入域以存儲某些狀態數據。當頁面被發回服務器,該服務器能解析,檢查其有效性并將這個狀態數據應用到頁面的控件樹中。可視狀態是一種非常強大的能力,因為它允許狀態被客戶端持續化并且它不需要cookies?或?服務器內存來存儲該狀態。許多?ASP.NET?服務器控件使用可視狀態來持續化與頁面元素交互期間所作的設置,例如,對數據進行分頁時保存當前頁顯示頁。
然而,使用可視狀態有許多不利之處,首先,不論是在請求的時候還是提供服務的時候,它都增加造成整個頁面的負擔。當序列化或反序列化被返回服務器的可視狀態數據時還產生一些附加的開銷。最終可視狀態會增加服務器的內存分配。
最著名的服務器控件要數?DataGrid?了,使用可視狀態有過之而無不及,即便是在不需要使用的時候也是如此。ViewState?屬性默認是啟用的,但如果你不需要它,可以在頁面控件級或頁面級關閉它。在某個控件中,只要將?EnableViewState?設置為?false,或者在頁面里使用如下全局設置:
<%@?Page?EnableViewState="false"?%>
如果在某頁面中不進行回發,或每次請求頁面時總是重新產生控件,那么你應該在頁面級禁用可視狀態。
結論
我已經向你提供了一些我認為有用的編寫高性能?ASP.NET?應用程序的技巧。正如我在本文開頭時所講的那樣,這是一些很初級的指南,而不是?ASP.NET?性能方面的最終定論。(更多有關改進?ASP.NET?應用程序性能方面的信息請參見:Improving?ASP.NET?Performance.)只有通過自己的經驗方能找到最佳途徑來解決具體的性能問題。不管怎樣,在你解決問題的過程中,這些技巧多少會對你有所裨益的。在軟件開發過程中,每一個應用都有其獨特的一面,沒有什么東西是絕對的。
——常見的性能神話
最常見的神話之一是?C#?代碼比?Visual?Basic?代碼快。這樣的說法是站不住腳的,雖然在?Visual?Basic?中存在一些?C#?沒有的性能阻礙行為,比如顯式地聲明類型。但是如果遵循良好的編程實踐,沒有理由說明?Visual?Basic?和?C#?代碼不能以幾乎同樣的性能執行。簡單說來,相同的代碼產生相同的結果。
另一個神話是后臺代碼比內聯代碼快,這是絕對不成立的。性能與你的?ASP.NET?應用程序代碼在哪沒有什么關系,無論是后臺代碼文件還是內聯在?ASP.NET?頁面。有時我更喜歡使用內聯代碼,因為變更不會產生后臺代碼那樣的更新成本。例如,使用后臺代碼必須更新整個后臺?DLL,那時一個可能引起驚慌的主張。
第三個神話是組件比頁面要快。這在經典的?ASP?中是存在的,因為編譯的?COM?服務器要比?VBScript?快得多。但是對于頁面和組件都是類的?ASP.NET?來說則不然。不論你的代碼是以后臺代碼形式內聯在頁面,還是分離的組件,所產生的性能差別不大。只是這種組織形式能更好地從邏輯上對功能進行分組,在性能上沒有差別。
我想澄清的最后一個神話是用?Web?服務來實現兩個應用程序之間各個功能。Web?服務應該被用于連接異構系統或提供系統功能及行為的遠程訪問。不應該將它用于兩個相同系統的內部連接。雖然使用起來很容易,但有很多其它更好的可選方法。最糟的事情莫過于將?Web?服務用于相同服務器上?ASP?和?ASP.NET?應用程序之間的通訊,我已經不厭其煩地對之進行了說明。
?
?作者簡介
Rob?Howard?是?Telligent?Systems?創建者,擅長于高性能?Web?應用以及知識管理和協作系統。此前?Rob?受雇于微軟,協助設計了?ASP.NET?1.0,1.1?和?2.0。你可以通過?rhoward@telligentsystems.com?與?Rob?聯系。
本文討論:
常見的?ASP.NET?性能神話?
有用的?ASP.NET?性能技巧和訣竅?
在?ASP.NET?中處理數據庫的一些建議?
緩沖以及用?ASP.NET?進行后臺處理?
本文使用下列技術:ASP.NET,.NET?框架,IIS
用?ASP.NET?編寫?Web?應用程序其輕松程度令人難以置信。它是如此的容易,以至于許多開發人員不用花費多少時間來構筑其應用便能獲得非常好的性能。在本文中,我將給出10個編寫高性能?Web?應用的技巧。我的評論不僅僅局限與?ASP.NET?應用,因為它們只是?Web?應用的一個子集。本文也不是?Web?應用性能調整的權威指南——這方面的內容可以寫成一本書。相反,本文可以被視作一個好的起點。
在廢寢忘食地工作之前,我常常要去攀巖。在攀巖之前,我總是要看一下指南手冊中的線路并閱讀以前來此一游的人留下的建議和忠告。但是,不管指南手冊有多磨好,在嘗試一次特定的具有挑戰性的攀爬之前,你都必須付諸實際的行動。同樣,在你面臨解決的性能問題或者營運一個高吞吐量的站點之前,你只能想方設法編寫高性能?Web?應用程序。
我們個人經驗來自在微軟?ASP.NET?團隊從事底層架構程序經理,運行和管理?www.asp.net?,并協助架構?Community?Server?過程中的經歷,Community?Server?是幾個有名的?ASP.NET?應用程序的下一個版本(它將?ASP.NET?Forums,.Text?和?nGallery?整合到一個平臺)。我確信這些幫助過我的技巧也會對你有所裨益。
你應該考慮將應用程序分離成幾個邏輯層。你可能聽說過術語3-層(或n-層)物理體系結構。它們通常是跨進程和/或硬件對功能進行物理劃分的規定的體系結構模式。當系統需要伸縮時,更多的硬件能被添加。然而,總是應該避免與進程和機器忙碌程度相關的性能問題。所以,不管什么時候,只要可能,都要在相同的應用中一起運行?ASP.NET?頁面及其相關的組件。
由于代碼和層之間的邊界分離,使用?Web?服務或遠程調用將降低20%以上的性能。
數據層則稍微有些不同,因為數據庫通常都用專門的硬件。但是,數據庫的處理成本仍然很高,因此最優化代碼時,數據層的性能應該是首當其充要關注的地方。
在著手解決你的應用程序的性能問題之前,一定要剖析應用程序,確定問題之所在。獲取關鍵的性能計數器值(如實現垃圾收集所花時間之百分比的性能計數器的值)對于查找應用程序在何處最耗時也是非常重要的。憑借直覺常常也能找到耗時所在。
本文所描述的性能改進有兩種類型:大型優化,如使用?ASP.NET?Cache,以及不斷重復進行的微型優化。這些微型優化有時很有意思。對代碼的小小改動便會引起很大的動靜,產生成千次的調用。對于大型優化,你可能會看到整體性能的大跳躍。而對微型優化,給定請求可能只是毫秒級的調整,但按每天的請求總數計算,其結果的改進可能是巨大的。
數據層的性能
當調整某個應用程序的性能時,有一個簡單的試金石,你可以用它按先后次序:檢查代碼是否存取數據庫?如果是,多長時間存取一次?注意相同的測試也可以被應用于使用?Web?服務或遠程調用的代碼,但我們本文中不涉及這方面內容。
如果在特定的代碼流程中必須具有對數據庫的請求以及要考察其它方面,如:想對字符串處理進行優先優化,那么暫且把它放一放,先按照上面定好的優先次序來做。除非你有異乎尋常的性能問題,否則你的時間應該用在嘗試最優化與數據庫的連接所花的時間,返回的數據量以及多長時間往返一次和數據庫的通訊上。
有了這些概括信息,下面就讓我們來看看能幫助你改善應用程序性能的十個技巧。我將從能獲得最顯著效果的改變開始。
技巧?1?——?返回多個結果集
復審你的數據庫代碼,看看是否有多于一次的對數據庫的訪問請求。這樣每次往返數據庫都降低你的應用程序能處理的每秒請求數。通過在單個數據庫請求中返回多結果集,你能降低與數據庫通信的總體時間。同時你也將使系統更具伸縮性,因為你減少了數據庫服務器處理請求的負擔。
雖然你可以用動態?SQL?返回多結果集,我更喜歡使用存儲過過程。是否將業務邏輯駐留在存儲過程當中是個有待爭論的問題,但我認為,如果存儲過程中的邏輯能約束返回的數據(降低數據集的尺寸,在網絡上傳輸的時間以及邏輯層不必過慮數據),這是一件好事情。
使用?SqlCommand?命令實例及其?ExecuteReader?方法來處理強類型的各個業務類,你通過調用?NextResult?可以向前移動結果集指針。Figure?1?示范了處理幾個帶類型的?ArrayLists?例子會話。從數據庫只返回你需要的數據還會降低服務器上內存的分配。
技巧?2?——?分頁數據存取
ASP.NET?DataGrid?提供了非常好的能力:數據分頁支持。當啟用?DataGrid?中的分頁功能,則每次只顯示固定數量的記錄。此外,分頁用戶界面也會顯示在?DataGrid?底部用于導航記錄。分頁用戶界面允許你向前向后導航所顯示的記錄,一次顯示固定數量的記錄。
有一個美中不足的是用?DataGrid?分頁需要將所有數據邦定到此柵格控件(gird)。例如,你的數據層必須返回所有數據,然后?DataGrid?將根據當前頁過濾掉所有顯示的記錄。當你通過?DataGrid?進行分頁時,如果有?100,000?條記錄被返回,那么每個請求有?99,975?條記錄將被廢棄掉(假設頁尺寸為?25)。當記錄數不斷增加,此應用程序的性能便會遭受痛苦,因為每次請求所要發送的數據會越來越多。
編寫較好的分頁代碼的一個好的方法是用存儲過程。Figure?2?示范了一個用?Northwind?數據庫中?Orders?表通過存儲過程分頁的例子。很簡單,只要你在頁面中傳遞索引以及頁尺寸即可。相應的結果集先被計算然后被返回。
在?Community?Server?中,我們編寫了幾個分頁控件來完成數據分頁。你將會看到,我使用了技巧?1?中討論的思路,從一個存儲過程中返回連個結果集:總記錄數和請求的數據。
返回的總記錄數依賴于所執行的查詢不同而不同。例如,某個?WHERE?子句可被用于約束返回的數據。為了計算在分頁用戶界面顯示的總頁數,返回的總記錄數必須是已知的。例如,如果有?1,000,000?條記錄,用一個?WHERE?子句對之過濾后為?1,000?條記錄,則分頁邏輯必須要知道總記錄數以便在分頁用戶界面中正確呈現。
技巧?3?——?連接池
建立?Web?應用程序與?SQL?Server?之間的?TCP?連接是一項昂貴的操作。微軟的開發人員利用連接池技術已經有好長一段時間了,這個技術使他們能重用到數據庫的連接。而不是每次請求都建立新的?TCP?連接,新連接僅在連接池中得不到連接時才建立。當連接被關閉時,它被返回到連接池中,在那里它仍然保持與數據庫的連接,與完全斷開?TCP?連接相反。
當然,你需要提防泄漏的連接。當你處理完畢,一定要關閉連接。重申一次:不管人們怎么吹噓微軟?.NET?框架中的垃圾收集特性,每當你處理完畢,一定要顯式地調用連接對象的?Close?或?Dispose?方法。不要指望公共語言運行時(CLR)來為你定時清除和關閉連接。CLR?最終將銷毀類并強行關閉連接,但你無法保證該對象的垃圾收集屆時會起作用。
為了充分用好連接池,有幾條規則必須了然于心。首先,打開連接,進行處理,然后關閉連接。寧愿每個請求的連接打開和關閉多次,也不要保持連接打開狀態以及在不同的方法間將它傳來傳去。其次,使用相同的連接串(如果你使用集成身份檢查,那么也要用相同的線程身份)。如果你不用相同的連接串,例如,根據登錄用戶來定制連接串,你將無法得到連接池所提供的相同的最優化值。當模擬大用戶量情形時,如果你使用集成身份檢查,那么你的連接池將效力大減。.NET?CLR?數據性能計數器在試圖跟蹤任何與連接池有關的性能問題時是非常有用的。
不管什么時候,只要你的應用程序連接到運行在其它進程中的資源,比如某個數據庫,你都應該針對連接到資源所耗時間,發送和接收數據所耗時間以及往返次數進行優化。為了實現較好的性能,應該首當其充優化應用程序中任何種類的忙碌進程。
應用層包含到數據層的連接以及將數據轉換成有意義的類實例和業務處理的邏輯。以?Community?Server?為例,你要在其中處理?Forums?和?Threads?集合;以及應用許可這樣的業務規則;尤其重要的是緩沖(Caching)邏輯也實現其中。
技巧?4?——?ASP.NET?Cache?API
在編寫代碼之前要做的頭等大事之一是最大限度地構建應用層并發掘?ASP.NET?的?Cache?特性。
如果你的組件在?ASP.NET?應用程序內運行,那么你只需要在應用程序工程中引用?System.Web.dll?即可。當你需要訪問?Cache?時,用?HttpRuntime.Cache?屬性(相同的對象也可以通過?Page.Cache?和?HttpContext.Cache?訪問)。
緩沖數據有幾個準則。首先,如果數據能被使用多次,緩沖是個好的后選方案。其次,如果數據對給定請求或用戶是一般的數據而非專用數據,那么最好是選擇緩沖。如果數據用戶或請求專用,如果需要保存期很長但可能不被經常使用,那么仍然要用緩沖。第三,常常被忽略的一個準則是有時緩沖太多的東西。一般來說,在x86機器上,為了降低內存不足錯誤的幾率,運行某個進程不要超過800MB私有字節。因此,緩沖應該有個上限。換句話說,你也許能重用某個計算的結果,但如果該計算有10個參數,你可能試圖針對10個置換進行緩沖,這樣做可能會給你帶來麻煩。ASP.NET?提供的最常見的容錯是由覆蓋緩沖導致的內存不足錯誤,尤其是大型數據集。
Cache?有幾個重要特性是必須要了解的。第一個是?Cache?實現了最近最少使用(least-recently-used)算法,允許?ASP.NET?強制?Cache?清除操作?——?如果可用內存下降到低水平?——?則自動從?Cache?中刪除不使用的項目。第二個是?Cache?支持依賴性到期特性,它能強制包括時間,鍵值,文件失效。時間常常被使用,但?ASP.NET?2.0?引入了具有更強大的失效類型:數據庫緩沖失效。也就是當數據庫中的數據改變時,緩沖中的條目會自動刪除。有關數據庫緩沖失效的更多信息參見?Dino?Esposito?在?MSDN?雜志?2004?年七月刊的?Cutting?Edge?專欄文章。該緩沖的體系結構,參見?Figure?3。
Figure?3?ASP.NET?Cache
技巧?5?——?預請求緩沖(Per-Request?Caching)
在本文前面,我曾提到對頻繁執行的代碼塊所做的小小改動可能產生很大的,整體性能的提升。我把其中一個我特別中意的叫做預請求緩沖(per-request?caching)。
由于?Cache?API??被設計用來緩沖長期數據或直到某個條件被滿足,預請求緩沖意旨用于請求期間的緩沖該數據。特定的代碼流程被每次請求頻繁訪問但是數據只需要被拾取,應用,修改或更新一次,這樣說太理論化,還是讓我們看一個具體的例子吧。
在?Community?Server?的?Forums?(論壇)應用中,某個頁面上使用的每個服務器控件需要個性化數據以確定使用那個皮膚和式樣頁,以及其它的個性化數據,其中有些數據可以被長時間緩沖,但有些數據,比如用于控件的皮膚在單個請求中只被拾取一次并在該請求執行期間被重用多次。
為了完成預請求緩沖,用?ASP.NET?HttpContext。HttpContext?的實例是隨每個請求創建的,并可以通過?HttpContext.Current?屬性在那個請求執行期間的任何地方存取它。HttpContext?類具有一個特別的?Items??集合屬性,被添加到該?Items??集合的對象和數據只是在該請求期間被緩存。就像你可以使用?Cache?來保存頻繁使用的數據一樣,你可以用?HttpContext.Items?來保存只在某個預請求中使用的數據。在此背景后的邏輯很簡單:當數據不存在時被添加到?HttpContext.Items?集合,以及在隨后的并發查找中簡單地返回?HttpContext.Items?中發現的數據。
技巧??6——后臺處理
你的代碼流程應該盡可能快,對吧?你自己可能多次發現要完成每個請求或每n個請求的任務代價很高。發出?e-mail?或解析并檢查輸入數據的有效性就是個例。
在重新生成?ASP.NET?Forums?1.0?并把它整合到?Community?Server?時,我們發現添加新貼的代碼流程非常慢。每次添加帖子,應用程序首先要確保沒有重復貼,然后必須用“badword”過濾器解析該貼的表情圖像,記號并索引,如果必要還要將帖子添加到相應的隊列中,對附件進行有效性檢查,最終完成發貼后,給預訂者發出?e-mail?通知。顯然,這里做的工作太多。
我們發現大多數時間都花在了索引邏輯和發送e-mail上。索引帖子是一個很耗時的操作,此外,內建的?System.Web.Mail?功能要與?SMTP?服務器連接并順序發送郵件。當特定帖子或主題預定者數量增加時,AddPost?函數的執行時間會越來越長。
并不是每個請求都需要索引郵件,我們想最好是批量集中處理,并且一次只索引25個帖子或每隔五分鐘發送一次郵件。我們決定使用的代碼與我曾在原型數據庫緩沖失效中所使用的代碼相同,最終它也被納入?Visual?Studio?2005。
名字空間?System.Threading?中的?Timer?類非常有用,但在.NET?框架中鮮為人知,至少對?Web?開發者來說是這樣。一旦創建,Timer?將以可定制的間隔針對線程池中的某個線程調用指定的回調函數。這意味著你不用輸入請求到?ASP.NET?應用程序便能讓代碼實行,這是一種最合適后臺處理的情形。你也可以在這種后臺處理模式中進行例如索引或發送電子郵件這樣的工作。
盡管如此,這個技術存在幾個問題,如果你的應用程序域關閉,該定時器實例將停止觸發其事件。另外,由于?CLR?有一個硬坎,即每個進程的線程數是固定的,你便可能陷入嚴重的服務器負荷當中,此時可能就沒有線程來處理定時器,從而造成延時。為了讓發生這種情況的幾率最小化,ASP.NET?通過在進程中預留一定數量的空閑線程,并只使用部分線程來處理請求。然而,如果你有許多異步處理,這樣做會有問題。
由于篇幅所限,在此無法列出代碼,但你可以從?www.rob-howard.net?下載可消化的例子。其中有?Blackbelt?TechEd?2004?展示的幻燈和?Demo。
技巧?7——頁面輸出緩存和代理服務器
ASP.NET?是你的表示層(或者說應該是);它由頁面,用戶控件,服務器控件(HttpHandlers?and?HttpModules)以及它們生成的內容組成。如果你有一個產生輸出的?ASP.NET?頁面,不管是輸出?HTML,XML,圖像還是任何其它數據,而且每個請求你都運行這個代碼并產生相同的輸出,此時最好選擇使用頁面輸出緩存。
只要在頁面頂部添加這一行代碼即可:
<%@?Page?OutputCache?VaryByParams="none"?Duration="60"?%>?
你可以為此頁面有效地產生一次輸出并可以在60秒內多次重用它,一到這個時間點,該頁面將重新執行并將再次將輸出添加到?ASP.NET?Cache。這個行為還能用某些低級編程?APIs?來完成。輸出緩存有幾個可以配置的設置,比如:VaryByParams?屬性。VaryByParams?不是必須的,但允許你指定?HTTP?GET?或?HTTP?POST?參數來改變緩存入口。例如,default.aspx?Report=1?或?default.aspx?Report=2?可以簡單地設置?VaryByParam="Report"?來對輸出進行緩存。額外的參數被命名并用用分號分隔。
在使用輸出緩存機制時,許多人都不了解?ASP.NET?頁還產生一組下游緩存服務器?HTTP?頭,比如?Microsoft?Internet?Security?and?Acceleration?Server?或?Akamai?使用的?HTTP?頭。當設置?HTTP?緩存頭,文檔可以被緩存到這些網絡資源,從而響應客戶端請求不必返回原服務器。
然而,使用頁面輸出緩存并不會使你的應用程序更有效率,但它能通過下游緩存技術緩存文檔從而潛在地降低服務器的負載。當然,這只能是異步內容;一旦實施下游緩存,你將無法看到任何請求,也不能實現身份認證來防止對它的存取。
技巧?8——運行?IIS?6.0?(如果僅用于內核緩存)
如果你不運行?IIS?6.O(Windows?Server?2003),那么你將得不到微軟?Web?服務器中一些重大的性能改進。在技巧?7?中,我談到了輸出緩存。在?IIS?5.0?中,請求到達?IIS,然后到達?ASP.NET。當使用緩存時,ASP.NET?中的?HttpModule?接受該請求,并從該緩存中返回內容。
如果你用?IIS?6.0,有一些巧妙的特性叫內核緩存,它不需要將任何代碼改成?ASP.NET。當?ASP.NET對請求進行緩存處理,IIS?內核緩存便接收一份緩存數據的拷貝。當請求來自網絡驅動器,內核一級的驅動程序(沒有到用戶模式的上下文轉換)接收該請求,如果緩存,則直接用緩存數據響應并完成執行。這意味著當你使用?IIS?內核模式緩存和?ASP.NET?緩存時,你將看到無法置信的性能結果。在開發?Visual?Studio?2005?的?ASP.NET?期間,我是負責?ASP.NET?性能的程序經理。開發人員的工作做的真是棒極了,而我基本上每天都在看報告。內核模式緩存結果總是最有趣的。典型的情況是請求/響應往往使網絡飽和,但?IIS?的運行僅占?CPU?的百分之五。真令人驚異!當然使用?IIS?6.O?有其它一些原因,但內核模式緩存是顯而易見的理由。
技巧?9——使用?Gzip?壓縮
雖然使用?gzip?壓縮不是一個必須的服務器性能技巧(因為你可能看到?CUP?的使用率上升了),但它能降低服務器發送字節的數量。從而感覺頁面更快,而且減少帶寬的占用。其壓縮的效果好壞取決于所發送的數據以及客戶端瀏覽器是否支持這種壓縮(IIS?只會將數據發送到支持?gzip?的瀏覽器,比如:IE?6.0?和?Firefox),從而使服務器可以在每秒鐘里處理更多的請求。事實上,只要你降低返回數據的數量,便能提高每秒所處理的請求數。
有一個好消息是?gzip?壓縮是?IIS?6.0?的內建特性,并且比它在?IIS?5.0?中使用的效果更好。但是,要想在?IIS?6.0?中啟用?gzip?壓縮可能沒那么方便,IIS?的屬性對話框里找不到設置它的地方。IIS?團隊將卓越的?gzip?壓縮能力內建在服務器中,但忽視了建立一個啟用壓縮特性的管理用戶界面。要想啟用?gzip?壓縮機制,你必須深入到?IIS?的?XML?配置設置內部(必須對之相當熟悉才能配置)。順便提一下,在此感謝?OrcsWeb?的?Scott?Forsyth?幫我解決了在?OrcsWeb?數個?www.asp.net?服務器上的這個問題。
與其在本文中包含整個過程,還不如閱讀?Brad?Wilson?在?IIS6?Compression?上的文章。微軟知識庫也有一篇關于為ASPX啟用壓縮特性的文章:Enable?ASPX?Compression?in?IIS。但是,還必須注意一點,動態壓縮與內核緩存由于某些實現細節的原因,其在?IIS?6.0?中是相互排斥的。
技巧?10——服務器控件的可視狀態
可視狀態(View?State)對于?ASP.NET?來說是個奇特的名字,它在所產生的頁面中隱藏輸入域以存儲某些狀態數據。當頁面被發回服務器,該服務器能解析,檢查其有效性并將這個狀態數據應用到頁面的控件樹中。可視狀態是一種非常強大的能力,因為它允許狀態被客戶端持續化并且它不需要cookies?或?服務器內存來存儲該狀態。許多?ASP.NET?服務器控件使用可視狀態來持續化與頁面元素交互期間所作的設置,例如,對數據進行分頁時保存當前頁顯示頁。
然而,使用可視狀態有許多不利之處,首先,不論是在請求的時候還是提供服務的時候,它都增加造成整個頁面的負擔。當序列化或反序列化被返回服務器的可視狀態數據時還產生一些附加的開銷。最終可視狀態會增加服務器的內存分配。
最著名的服務器控件要數?DataGrid?了,使用可視狀態有過之而無不及,即便是在不需要使用的時候也是如此。ViewState?屬性默認是啟用的,但如果你不需要它,可以在頁面控件級或頁面級關閉它。在某個控件中,只要將?EnableViewState?設置為?false,或者在頁面里使用如下全局設置:
<%@?Page?EnableViewState="false"?%>
如果在某頁面中不進行回發,或每次請求頁面時總是重新產生控件,那么你應該在頁面級禁用可視狀態。
結論
我已經向你提供了一些我認為有用的編寫高性能?ASP.NET?應用程序的技巧。正如我在本文開頭時所講的那樣,這是一些很初級的指南,而不是?ASP.NET?性能方面的最終定論。(更多有關改進?ASP.NET?應用程序性能方面的信息請參見:Improving?ASP.NET?Performance.)只有通過自己的經驗方能找到最佳途徑來解決具體的性能問題。不管怎樣,在你解決問題的過程中,這些技巧多少會對你有所裨益的。在軟件開發過程中,每一個應用都有其獨特的一面,沒有什么東西是絕對的。
——常見的性能神話
最常見的神話之一是?C#?代碼比?Visual?Basic?代碼快。這樣的說法是站不住腳的,雖然在?Visual?Basic?中存在一些?C#?沒有的性能阻礙行為,比如顯式地聲明類型。但是如果遵循良好的編程實踐,沒有理由說明?Visual?Basic?和?C#?代碼不能以幾乎同樣的性能執行。簡單說來,相同的代碼產生相同的結果。
另一個神話是后臺代碼比內聯代碼快,這是絕對不成立的。性能與你的?ASP.NET?應用程序代碼在哪沒有什么關系,無論是后臺代碼文件還是內聯在?ASP.NET?頁面。有時我更喜歡使用內聯代碼,因為變更不會產生后臺代碼那樣的更新成本。例如,使用后臺代碼必須更新整個后臺?DLL,那時一個可能引起驚慌的主張。
第三個神話是組件比頁面要快。這在經典的?ASP?中是存在的,因為編譯的?COM?服務器要比?VBScript?快得多。但是對于頁面和組件都是類的?ASP.NET?來說則不然。不論你的代碼是以后臺代碼形式內聯在頁面,還是分離的組件,所產生的性能差別不大。只是這種組織形式能更好地從邏輯上對功能進行分組,在性能上沒有差別。
我想澄清的最后一個神話是用?Web?服務來實現兩個應用程序之間各個功能。Web?服務應該被用于連接異構系統或提供系統功能及行為的遠程訪問。不應該將它用于兩個相同系統的內部連接。雖然使用起來很容易,但有很多其它更好的可選方法。最糟的事情莫過于將?Web?服務用于相同服務器上?ASP?和?ASP.NET?應用程序之間的通訊,我已經不厭其煩地對之進行了說明。
?
?作者簡介
Rob?Howard?是?Telligent?Systems?創建者,擅長于高性能?Web?應用以及知識管理和協作系統。此前?Rob?受雇于微軟,協助設計了?ASP.NET?1.0,1.1?和?2.0。你可以通過?rhoward@telligentsystems.com?與?Rob?聯系。
轉載于:https://www.cnblogs.com/dagon007/archive/2005/03/10/116013.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的编写高性能 Web 应用程序的 10 个技巧 (转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 速达5000出现计算成本数据溢出的问题
- 下一篇: es6拼接字符串的方式。