Asp.NET有許多秘密,當你瞭解了這些秘密後,可以使得你的ASP.NET應用程式達到極大的效能提升。舉例來說,在使用Membership和profile進行Authentication和authorization時,可以通過簡單的修改Membership和profile以提高Authentication和authorization的效能;ASP.NET Http 管道通過調整以避免執行不必要的HttpModules;不僅如此,瀏覽器端的頁面緩衝可以為重複瀏覽節省大量的下載時間;按需UI載入可以使網站更快、更平滑;最後,t適當的應用CDN(Cotent Delivery Networks)和應用Http Cache Header可以使網站更加快速。下面就讓我們來分別討論這些技巧,以提高我們ASP.NET網站的效能。
1 ASP.NET 管道最佳化配置
使用者每次請求網站時,都會有許多ASP.NET預設HttpModules去插斷要求並進行一些處理,例如SessisonStateModule會中斷每次的請求,解析session cookie資訊並載入的適當的Session給HttpContent.但是這些預設的HttpModules並不都是必須的,例如如果你沒有應用Forms Authentication來進行使用者驗證,那就不需要FormsAuthentication Module。
預設的Modules是在machine.config檔案(該檔案的位置為:$WINDOWS$\Microsoft.NET\Framework\$VERSION$\CONFIG directory))定義的,其定義如下:.
<httpModules>
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule" />
<add name="Session" type="System.Web.SessionState.SessionStateModule" />
<add name="WindowsAuthentication"
type="System.Web.Security.WindowsAuthenticationModule" />
<add name="FormsAuthentication"
type="System.Web.Security.FormsAuthenticationModule" />
<add name="PassportAuthentication"
type="System.Web.Security.PassportAuthenticationModule" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
<add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule,
System.Web.Mobile, Version=1.0.5000.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</httpModules>
你要以通過在web.config中添加<remove>節點來移除不必要的httpModules,比如如果一個網站使用的是基於資料庫的forms authentication驗證並且沒有使用任何Session則可以在web.config中添加如下節點:
<httpModules>
<!-- Remove unnecessary Http Modules for faster pipeline -->
<remove name="Session" />
<remove name="WindowsAuthentication" />
<remove name="PassportAuthentication" />
<remove name="AnonymousIdentification" />
<remove name="UrlAuthorization" />
<remove name="FileAuthorization" />
</httpModules>
2 ASP.NET進程最佳化配置
ASP.NET進程模組配置了許多進程層級的屬性,比如Asp.NET可以使用多少線程;逾時多長時間後阻塞一個線程;在IO線程完成工作時,應該由多少個請求保持等待。預設的配置有許多限制,但是現在硬體變得越來越便宜,雙核處理和GB的記憶體變成了標準配置,所以我們要以調整預設配置,讓Asp.net進程使用更多的系統資源。
預設情況下,mechine.config檔案中是這樣配置asp.net進程的。
<system.web>
<processModel autoConfig="true" />
我們可以調整預設配置,為每個屬性指定不同的值,以定製asp.net線程的工作模型,例如:
<processModel
enable="true"
timeout="Infinite"
idleTimeout="Infinite"
shutdownTimeout="00:00:05"
requestLimit="Infinite"
requestQueueLimit="5000"
restartQueueLimit="10"
memoryLimit="60"
webGarden="false"
cpuMask="0xffffffff"
userName="machine"
password="AutoGenerate"
logLevel="Errors"
clientConnectedCheck="00:00:05"
comAuthenticationLevel="Connect"
comImpersonationLevel="Impersonate"
responseDeadlockInterval="00:03:00"
responseRestartDeadlockInterval="00:03:00"
autoConfig="false"
maxWorkerThreads="100"
maxIoThreads="100"
minWorkerThreads="40"
minIoThreads="30"
serverErrorMessageFile=""
pingFrequency="Infinite"
pingTimeout="Infinite"
asyncOption="20"
maxAppDomains="2000"
/>
上面的屬性基本上都是使用的預設值,除了下面的幾個:
- maxWorkerThreads :maxWorkerThreads的預設值是20/進程。在一個雙核處理器的伺服器中,會有40個線程分配給asp.net,這意味著雙核伺服器的asp.net在同一時間只能平行處理40個請求,為了分配給asp.net更多的線程,我把maxWorkerThreads的值改為100。特別需要注意的是,如果asp.net用完了線程,這時如果有新的請求進來,asp.net會將新來的請求放到等待隊列,直到有線程釋放為止,所以如果你的應用調用了許多web service或是有許多i/o操作,並且這些操作不會佔用太多的cpu資源的話,你完全可以增大maxWorkerThreads的值。
- maxIOThreads :maxIOThreads的預設值也是20/process,在一個雙核處理器上,同樣也有40個線程分配給asp.net來進行I/O操作,這也意味著在一個雙核處理器上,同一時間asp.net只能平行處理40個I/O請求。這裡我們說的I/O請求可以是檔案讀寫、資料庫操作、web service調用等。所以如果你的應用程式伺服器有足夠的資源來平行處理這些操作,你完全可以將maxIOThreads的值設的大一些,比如100.
- minWorkerThreads :minWorkerThreads的預設值為1,它指出了分配給asp.net的最少輔助線程數。
- minIOThreads : minWorkerThreads的預設值為1,它指出了分配給asp.net的最少I/O輔助線程數。
- memoryLimit:以百分比的形式指定在 ASP.NET 啟動新進程和重新分配現有請求前,輔助進程可以使用的最大記憶體大小。memoryLimit的預設值是60,如果你的伺服器上只有你自己的應用程式,並且沒有別的消耗記憶體的服務,那麼你完全可以將memoryLimit設的大一些,比如80。
除了processModel之外,還有另外一個重要的節點要以配置,就是你可以在System.Net節點配置對一個IP所允許的最大並發請求數:
<system.net>
<connectionManagement>
<add address="*" maxconnection="100" />
</connectionManagement>
</system.net>
maxconnection的預設值是2,這意味著你不能在你的應用程式裡同時對一個IP進行多於2個的請求。所以如果你的應用對一個特定的伺服器進行了許多調用,你完全可以將maxconnection的值設的大一點,例如我在這裡設為100。
3 在應用程式上線前必須做的事情
如果你的應用程式中應用了Membership Provider,那麼在應用程式上線前,你不得不對web.config做一些調整:
- 在Profile Provider中添加applicationname屬性,如果你沒有指定applicationname的值,那麼profile Provider會將其值設為一個GUID,這意味著在你的開發機器上applicationname是一個GUID,在產品伺服器上application是另一個GUID,所以你就沒有重用開發機器上資料庫裡關於membership的資料。所以請按下面的格式指定applicationname的值:
<profile enabled="true">
<providers>
<clear />
<add name="..." type="System.Web.Profile.SqlProfileProvider"
connectionStringName="..." applicationName="YourApplicationName"
description="..." />
</providers>
- 每當一次請求完全,profile provider都會自動儲存profile,這樣我們的應用程式就因為大量的非必須的update操作降低了效能,所以關閉profile的自動儲存並且在需要時,顯示的通過Profile.Save()來儲存。
<profile enabled="true" automaticSaveEnabled="false" >
- Role Manager每次都需要查詢資料庫來確定一個使用者的角色,這同樣也是一個很大的效能損失,我們可以通過將使用者的角色緩衝在cookie中來解決這個問題,但是請注意cookie的最大容量是2KB,所以如果我們有許多使用者並且每個使用者都有特別多的角色,就有可能超出cookie的容量,當然這種情況畢竟是少數,所以通常我們可以通過下面的設定將使用者角色資訊緩衝在cookie中,以減少資料庫訪問次數。
<roleManager enabled="true" cacheRolesInCookie="true" >
4 在用戶端緩衝Ajax請求
瀏覽器可以緩衝圖片、js檔案、css檔案,同樣瀏覽器也可以緩衝XML Http調用(當然這需要XML Http以get方式發送調用),這種緩衝基於URL,當我們發送一個請求時,如果和以前請求的URL相同,那麼瀏覽器就會從緩衝中響應,而不是從重新請求伺服器,這可以節省一些時間。通常來說,瀏覽器會緩衝所有的Http get請求的結果,所以如果我們讓XML Http以get方式發送請求,並且伺服器返回特定的header告訴瀏覽器緩衝結果,那麼在以後的請求中,我們就可以直接從緩衝中得到響應,從而節約了大量的時間。
在PageFlakes網站中,作者緩衝了使用者狀態,所以如果使用者重新登入網站,使用者將會從瀏覽器緩衝中得到一個緩衝的頁面而不是重新請求伺服器,所以第二次訪問是快很多,另外作者還緩衝了許多使用者的操作結果,所以當使用者重新做相同的動作時,被緩衝的結果會迅速的出現,這就使使用者得到了更好的體驗。
所以如果以http get方式調用Ajax請求時,我們應該返回特定的http header以告訴瀏覽器緩衝請求的結果。下面是兩種不同的方式,我們可以採取其中之一:
HTTP/1.1 200 OK
Expires: Fri, 1 Jan 2030
Cache-Control: public
上面的header告訴瀏覽器將請求結果緩衝到2030年,所以如果我們在2030年之前發出同樣的xml http請求,瀏覽器都會直接從緩衝中讀取結果,而不用重新請求伺服器。另外還有一種更進階的header用來控制項請求結果的緩衝,下面的header指示瀏覽器也將請求結果緩衝60分鐘,並在60分鐘後重新請求結果,
HTTP/1.1 200 OK
Cache-Control: private, must-revalidate, proxy-revalidate, max-age=60
讓我們嘗試在Asp.NET web service響應中加入上述的header:
[WebMethod][ScriptMethod(UseHttpGet=true)]
public string CachedGet()
{
TimeSpan cacheDuration = TimeSpan.FromMinutes(1);
Context.Response.Cache.SetCacheability(HttpCacheability.Public);
Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
Context.Response.Cache.SetMaxAge(cacheDuration);
Context.Response.Cache.AppendCacheExtension(
"must-revalidate, proxy-revalidate");
return DateTime.Now.ToString();
}
上面的代碼將會產生如下的response headwers :
從我們可以看到,Expires屬性被正確的設定了,但是cache-control屬性卻沒有被正確設定,其max-age的值是0,而不是60,這樣瀏覽器就不會做任何緩衝,所以當我們重新請求時,還是會請求伺服器,而不是從緩衝中返回結果。
這是Asp.net 2.0的一個bug,你無法改變max-age的值,這是因為在處理一個web services請求之前,asp.net ajax framework截取了請求並且錯誤地將max-age設定為0,這就意味著瀏覽器不會緩衝ajax響應。
但是通過反編譯HttpCachePolicy的代碼,我們發現了如下程式碼片段:
不知怎麼地,只有在"if (!this._isMaxAgeSet || (delta < this._maxAge))" 時,才會設定_maxAge的值,這是不正確的,因為這阻止了將_maxAge的值設為一個大於當前值的值。所以我們需要通過反射來繞過SetMaxAge方法去設定_maxAge的值:
[WebMethod][ScriptMethod(UseHttpGet=true)]
public string CachedGet2()
{
TimeSpan cacheDuration = TimeSpan.FromMinutes(1);
FieldInfo maxAge = Context.Response.Cache.GetType().GetField("_maxAge",
BindingFlags.Instance|BindingFlags.NonPublic);
maxAge.SetValue(Context.Response.Cache, cacheDuration);
Context.Response.Cache.SetCacheability(HttpCacheability.Public);
Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
Context.Response.Cache.AppendCacheExtension(
"must-revalidate, proxy-revalidate");
return DateTime.Now.ToString();
}
上面的代碼返回如下的header:
現在我們可以看到 max-age的值是60,並且瀏覽器會緩衝結果60分鐘,如果你在60分鐘內進行相同的調用,瀏覽器將會從緩衝中響應。一分鐘後,緩衝到期,瀏覽器會自動發送一個請求,那個請求的代碼就像下面一樣:
function testCache()
{
TestService.CachedGet(function(result)
{
debug.trace(result);
});
}
請注意,這裡還有一個問題,因為asp.net ajax的預設信任層級是中等的,
<system.web>
<trust level="Medium"/>
而我們的CachedGet2方法卻用到了反射,所以僅有中等信任是不夠的,我們需要按如下的方式修改web.config檔案:
<system.web>
<trust level="Full"/>
5 更好的應用瀏覽器緩衝使用一致的URL
瀏覽器緩衝的基礎是URL,當一個URL改變時,瀏覽器就會從伺服器重新請求頁面。我們可以通過不同的查詢參數來改變URL,例如我們在緩衝中緩衝了default.aspx頁面,如果我們再請求default.aspx/?1頁面,這時雖然是相同的操作,但瀏覽器仍會重新請求伺服器,另外如果在Header中做了設定,新的請求結果也會被緩衝。因此我們要確保使用一致的URL。一個常見的錯誤是在是URL中省略www,例如www.cnblogs.com/zhangronghua和cnblogs.com/zhangronghua會被分別緩衝。
將靜態內容的緩衝周期設的長些
靜態檔案可以設定一個長的緩衝周期,比如一個月。如果你認為你應該緩衝兩天,這樣當你改變了檔案後,使用者就可以很快的得到新的檔案,這就你錯了,當你更新了一個被expires緩衝的檔案後,新使用者會立即得到更改後的檔案,而老使用者只有在緩衝到期後才能得到更改後的檔案。所以只要你應用了Expires去緩衝靜態檔案,就應該將到期周期設的儘可能地大。
例如如果你將一個靜態檔案的緩衝周期設為三天,一個使用者A在今天訪問頁面並將其緩衝三天,另一個使用者B在明天訪問頁面並同樣快取頁面面。所以如果你在後天改變了檔案,那麼使用者A將會在第四天看到新檔案,使用者B將會在第五天看到新檔案,這就會導致不同的使用者看到不同的檔案,這不是我們想要的結果,所以如果我們更改了檔案並將想讓使用者立即看到更改後的檔案,我們應該通過不同的URL來訪問檔案。
使用緩衝友好地目錄結構
盡量將需要緩衝的檔案放在一個目錄中,例如將所有的image放在images目錄中,而不是分別放在不同的目錄中,這樣我們就要以在整個網站中使用一致的URL來請求圖片。
重用公用圖片
有時我們為了少寫一些路徑,會將一個公用圖片放到許多不同的目錄中,這樣我們就會在檔案中只使用圖片名來引用圖片,而不是基於相同路徑,這樣做雖說方便了我們的書寫,卻不是緩衝友好的,公用檔案的每一個copy都會在緩衝中緩衝一份。所以我們需要做的就是將公用檔案放到一個統一的目錄中,在頁面中使用相對路徑來引用它。
當你修改靜態檔案後,記得修改檔案名稱
當你修改了一個靜態檔案後,不要以為修改好了檔案就萬事大吉了,因為靜態檔案會在用戶端緩衝,所以當你修改了靜態檔案後,一定要記得為其重新命名,因為這樣可 以確保使用者可以立即看到你修改後的檔案,而不是緩衝了瀏覽器緩衝中的修改前的檔案。
使用版本號碼去訪問靜態檔案
如果你不想每次修改靜態檔案名稱後都為其重新命名,那麼你也可以使用帶版本號碼的URL去訪問靜態檔案,例如GIF檔案可以通過帶啞查詢字元中的URL訪問,你可以通過/images/1.gif?v=1去訪問1.gif檔案,當1.gif檔案改變後,你要以通過/images/1.gif?v=2來訪問1.gif檔案,這樣也可以保證使用者會立即看到修改後的檔案。
將快取檔案放在單獨的域中
將快取檔案放在一個單獨的域中是一個好主意。首先瀏覽器也以並發的請求靜態檔案。其次你不必發送cookies給靜態檔案,當你將靜態檔案和應用程式放在同一個域中時,瀏覽器會在你請求靜態檔案時,將應用程式產生的cookies也一併發送,而這些cookies對靜態檔案不是必須的。所以如果我們將靜態檔案單獨放在一個域中,就要以將避免發送這些cookies。例如我們可以將靜態檔案放在www.zhangronghua.com中,而將我們的應用程式放在真正的www.application.com中,www.zhangronghua.com不必是一個真正的網站,它可以只是一個別名並且和www.application.com指向共同的目錄。
SSL 是非緩衝的,所以請適當使用
SSL是非緩衝的,所以不要通過SSL請求靜態檔案,另外,我們僅應該在登入頁面和支付頁面這樣的情境下使用SSL,除此之外不應該在程式中使用SSL。SSL需要加密請求和響應,所以增加了伺服器的負擔,同時加密後的響應內容也更長,會佔用更多的頻寬。
HTTP POST請求不會被緩衝
瀏覽器只會緩衝Http Get請求,不會緩衝Http Post請求,所以如果你想要緩衝一個檔案時,請確保以http get方式請求檔案。
顯示指明Content-Length 屬性
當你通過web service或是http hadler請求一個動態內容時,請確保設定了response header的content-length屬性。當瀏覽器從響應的header中讀取到content-lengtg屬性時,它會對響應做一些最佳化以加快下載速度。當指定了content-length後,瀏覽器會使用持續的串連請求資源,這就避免了瀏覽器針對每次請求都建立串連。
如何在IIS中緩衝靜態內容
在IIS管理器中,右擊應用程式名稱,選擇“屬性”,我們可以看到:
選擇“啟用內容到期”就可以緩衝靜態內容了。具體的設定很簡單,這裡就不說了。
6 總結
以上列舉了Asp.NET應用程式的部分最佳化方法,主要包括Asp.net管道最佳化配置、Asp.net線程最佳化配置和靜態檔案、Ajax調用緩衝。希望對大家所協助!