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