天天看點

【譯文】10 ASP.NET Performance and Scalability Secrets

Introduction介紹

ASPNET 2.0 有很多的秘密,當探究了之後,能夠給你帶來很大的性能級可測兩性的提升。例如,Membership和Profile provider 有一些秘密的瓶頸,能夠很容易的解決并且使得身份驗證和授權更加快速。此外,通過設定ASPNET HTTP 管道,能夠避免執行在每一個不必要的代碼。不僅是這樣,ASPNET工作程序可以把每一個降低性能的點排除,将性能推向極限。浏覽器中的頁面局部緩存(不是伺服器端),能夠在重複的請求中明顯的減少下載下傳量。一經請求,UI的加載能夠給你的站點一個快速流暢的感覺。最終,内容分發網絡 (CDN)和恰當的使用HTTP Cache 頭能夠使得你的網站非常快。在這篇文章中,你可以學習到這些技術,使得你的ASPNET應用程式有一個性能上面的提升,并且可以增加10到100倍的負載。

這篇文章中,我們将會讨論下面的技術:

  • ASPNET 管道優化
  • ASPNET程序配置優化
  • 上線之前,你必須要做的
  • Content Delivery Network
  • 浏覽器中緩存AJAX調用 
  • 最佳的利用浏覽器的緩存
  • 為了更加流暢的體驗,按照要求進行的UI加載
  • 優化ASPNET2.0 Profile provider
  • How to query ASP.NET 2.0 Membership tables without bringing down the site
  • 在不降低整個網站的性能的情況下,應該如何查詢ASPNET2.0
  • 防止DOS攻擊

上面的技術在任何一個ASPNET網站都可以被實作,尤其是那些使用了ASPNETMembership和Profile Provider的。

你可以了解到很多的關于ASPNET和ASPNET AJAX網站性能優化的東西,從我的書- Building a Web 2.0 portal using ASP.NET 3.5.

ASPNET管道優化

有一些ASPNET預設的HTTPModule,在請求管道中,而且在每一個請求都會攔截。舉個例子,

SessionStateModule

 攔截每一個請求,轉換Session Cookie并且加載相應的Session到HttpContext。并不是所有的這些Module總是必要的。舉個例子,如果你沒有使用Membership和Profile Provider你就不需要

FormsAuthentication

 module。如果你沒有為你的使用者使用Windows 身份驗證你就不需要

WindowsAuthentication。這些 module都在管道中,為每一個請求都執行一些不需要的代碼。

預設的Module在machine.config中定義(位于$WINDOWS$/Microsoft.NET/Framework/$VERSION$/CONFIG 目錄).

1

【譯文】10 ASP.NET Performance and Scalability Secrets

< httpModules >

2

【譯文】10 ASP.NET Performance and Scalability Secrets

  < add name = " OutputCache "  type = " System.Web.Caching.OutputCacheModule "   />

3

【譯文】10 ASP.NET Performance and Scalability Secrets

  < add name = " Session "  type = " System.Web.SessionState.SessionStateModule "   />

4

【譯文】10 ASP.NET Performance and Scalability Secrets

  < add name = " WindowsAuthentication "  

5

【譯文】10 ASP.NET Performance and Scalability Secrets

 type = " System.Web.Security.WindowsAuthenticationModule "   />

6

【譯文】10 ASP.NET Performance and Scalability Secrets

  < add name = " FormsAuthentication "  

7

【譯文】10 ASP.NET Performance and Scalability Secrets

 type = " System.Web.Security.FormsAuthenticationModule "   />

8

【譯文】10 ASP.NET Performance and Scalability Secrets

  < add name = " PassportAuthentication "  

9

【譯文】10 ASP.NET Performance and Scalability Secrets

 type = " System.Web.Security.PassportAuthenticationModule "   />

10

【譯文】10 ASP.NET Performance and Scalability Secrets

  < add name = " UrlAuthorization "  type = " System.Web.Security.UrlAuthorizationModule "   />

11

【譯文】10 ASP.NET Performance and Scalability Secrets

  < add name = " FileAuthorization "  type = " System.Web.Security.FileAuthorizationModule "   />

12

【譯文】10 ASP.NET Performance and Scalability Secrets

  < add name = " ErrorHandlerModule "  type = " System.Web.Mobile.ErrorHandlerModule, 

13

【譯文】10 ASP.NET Performance and Scalability Secrets

 System.Web.Mobile, Version = 1.0 . 5000.0 , 

14

【譯文】10 ASP.NET Performance and Scalability Secrets

 Culture = neutral, PublicKeyToken = b03f5f7f11d50a3a "  />

15

【譯文】10 ASP.NET Performance and Scalability Secrets

</ httpModules >

16

【譯文】10 ASP.NET Performance and Scalability Secrets

17

【譯文】10 ASP.NET Performance and Scalability Secrets

你可以出去這些預設的module,通過在你的應用程式的web.config中添加 

<remove>

 節點。例如:

1

【譯文】10 ASP.NET Performance and Scalability Secrets

< httpModules >

2

【譯文】10 ASP.NET Performance and Scalability Secrets

  <!--  Remove unnecessary Http Modules  for  faster pipeline  -->

3

【譯文】10 ASP.NET Performance and Scalability Secrets

  < remove name = " Session "   />

4

【譯文】10 ASP.NET Performance and Scalability Secrets

  < remove name = " WindowsAuthentication "   />

5

【譯文】10 ASP.NET Performance and Scalability Secrets

  < remove name = " PassportAuthentication "   />

6

【譯文】10 ASP.NET Performance and Scalability Secrets

  < remove name = " AnonymousIdentification "   />

7

【譯文】10 ASP.NET Performance and Scalability Secrets

  < remove name = " UrlAuthorization "   />

8

【譯文】10 ASP.NET Performance and Scalability Secrets

  < remove name = " FileAuthorization "   />

9

【譯文】10 ASP.NET Performance and Scalability Secrets

</ httpModules >

上面的配置适合使用資料庫的基于Form驗證并且不需要任何Session支援的站點。是以這些module都可以安全的被删除掉。

ASPNET程序配置優化

ASPNET程序模型配置定義了一些線程級别的屬性,像ASPNET使用多少個線程,在逾時之前等待線程多長時間,多少個請求等待IO工作的完成,等等。大多數情況下,預設的值都太小了。現在,硬體變得非常的便宜,雙核上G記憶體的伺服器已經變成了一個非常普遍的選擇。是以,程序模型配置允許使得ASPNET程序消耗更多的系統資源并且提供更好的吞吐量、

一個正常ASPNET安裝将會建立一個如下配置的machine.config 。

1

【譯文】10 ASP.NET Performance and Scalability Secrets

< system.web >

2

【譯文】10 ASP.NET Performance and Scalability Secrets

  < processModel autoConfig = " true "   />  

【譯文】10 ASP.NET Performance and Scalability Secrets

你需要設定這個自動配置,并且為一些屬性用特定的值,來定制ASPNET工作程序的工作。例如:

1

【譯文】10 ASP.NET Performance and Scalability Secrets

< processModel 

2

【譯文】10 ASP.NET Performance and Scalability Secrets

 enable = " true "  

3

【譯文】10 ASP.NET Performance and Scalability Secrets

 timeout = " Infinite "  

4

【譯文】10 ASP.NET Performance and Scalability Secrets

 idleTimeout = " Infinite "  

5

【譯文】10 ASP.NET Performance and Scalability Secrets

 shutdownTimeout = " 00:00:05 "  

6

【譯文】10 ASP.NET Performance and Scalability Secrets

 requestLimit = " Infinite "  

7

【譯文】10 ASP.NET Performance and Scalability Secrets

 requestQueueLimit = " 5000 "  

8

【譯文】10 ASP.NET Performance and Scalability Secrets

 restartQueueLimit = " 10 "  

9

【譯文】10 ASP.NET Performance and Scalability Secrets

 memoryLimit = " 60 "  

10

【譯文】10 ASP.NET Performance and Scalability Secrets

 webGarden = " false "  

11

【譯文】10 ASP.NET Performance and Scalability Secrets

 cpuMask = " 0xffffffff "  

12

【譯文】10 ASP.NET Performance and Scalability Secrets

 userName = " machine "  

13

【譯文】10 ASP.NET Performance and Scalability Secrets

 password = " AutoGenerate "  

14

【譯文】10 ASP.NET Performance and Scalability Secrets

 logLevel = " Errors "  

15

【譯文】10 ASP.NET Performance and Scalability Secrets

 clientConnectedCheck = " 00:00:05 "  

16

【譯文】10 ASP.NET Performance and Scalability Secrets

 comAuthenticationLevel = " Connect "  

17

【譯文】10 ASP.NET Performance and Scalability Secrets

 comImpersonationLevel = " Impersonate "  

18

【譯文】10 ASP.NET Performance and Scalability Secrets

 responseDeadlockInterval = " 00:03:00 "  

19

【譯文】10 ASP.NET Performance and Scalability Secrets

 responseRestartDeadlockInterval = " 00:03:00 "  

20

【譯文】10 ASP.NET Performance and Scalability Secrets

 autoConfig = " false "  

21

【譯文】10 ASP.NET Performance and Scalability Secrets

 maxWorkerThreads = " 100 "  

22

【譯文】10 ASP.NET Performance and Scalability Secrets

 maxIoThreads = " 100 "  

23

【譯文】10 ASP.NET Performance and Scalability Secrets

 minWorkerThreads = " 40 "  

24

【譯文】10 ASP.NET Performance and Scalability Secrets

 minIoThreads = " 30 "  

25

【譯文】10 ASP.NET Performance and Scalability Secrets

 serverErrorMessageFile = ""  

26

【譯文】10 ASP.NET Performance and Scalability Secrets

 pingFrequency = " Infinite "  

27

【譯文】10 ASP.NET Performance and Scalability Secrets

 pingTimeout = " Infinite "  

28

【譯文】10 ASP.NET Performance and Scalability Secrets

 asyncOption = " 20 "  

29

【譯文】10 ASP.NET Performance and Scalability Secrets

 maxAppDomains = " 2000 "  

30

【譯文】10 ASP.NET Performance and Scalability Secrets

/>

【譯文】10 ASP.NET Performance and Scalability Secrets

除去以下的,這裡的值都是預設的:

  • maxWorkerThreads

     - 這個預設值是20。在一個雙核的計算機中,将會有40個線程供ASPNET配置設定。這意味着,ASPNET可以同時處理40個請求。我将這個升到了100,以便給每一個ASPNET程序更多線程。如果你有一個應用程式,并不耗費CPU,并且能夠容易的在每秒處理更多請求,這樣,你可以增加這個值。尤其是當你的web應用程式用了很多的Web Service調用或者,沒有什麼CPU壓力的上傳/下載下傳很多資料的時候。當ASPNET運作超出了現成的數量,它将會停止處理其他請求。請求進入一個隊列,等待線程被釋放。這大概會發生在站點接收到比你預期的多得多的請求數量,如果你的CPU有空閑,那麼增加這個工作線程的數量。
  • maxIOThreads

     - 每一個程序預設。在一個雙核的計算機上面,将會有40個線程來為ASPNET進行I/O操作。這意味着在同一時間,ASPNET可以在一台雙核計算機上處理40個I/O請求。I/O請求可以使檔案的讀寫,資料庫操作,web service調用,以及從内部web應用程式生成的HTTP請求,等等。是以,你可以将這個值設成100,如果你的伺服器有足夠的系統資源。尤其是當你的web應用程式下載下傳/上傳資料,調用外部的web service。
  • minWorkerThreads

     - 當若幹個空閑的ASPNET工作線程小于這個數字,ASPNET開始将進入的請求排入隊列。是以,你能夠設定這個值為一個比較小的數字,來增加并發的請求。然而,不要将這個數字設定的太小,因為Web應用程式的的代碼需要做一些背後的處理,而且,并發請求需要一些空閑的工作線程。
  • minIOThreads

     - 和

    minWorkerThreads

     相同,這是對I/O線程的。然而,你可以設定一個比

    minWorkerThreads

     更小的數字,因為沒有并行處理I/O線程的問題、
  • memoryLimit

     - 指定允許使用的最大記憶體容量,是整個系統記憶體的百分比,訓示可以在運作一個新程序以及重新配置設定已經存在的請求之前,可以使用的記憶體。如果你隻有一個應用程式,而且沒有其他的程序需要記憶體,你可以設定一個更高的值,像80。然而,如果你有一個有漏洞的程式,而且它不斷的洩露記憶體,那麼你最好設定一個更小的值,可以使得這個有漏洞的程序盡快的回收,這樣,保證你的站點健康。尤其是,當你使用COM元件,消耗記憶體的時候。然而,這是一個臨時的解決方案,你當然會修複這個漏洞。

此外還有

processModel

, 這裡由一個非常重要的節點,在system.net,你可以指定,同一個IP最大數量上限的請求。

1

【譯文】10 ASP.NET Performance and Scalability Secrets

< system.net >

2

【譯文】10 ASP.NET Performance and Scalability Secrets

  < connectionManagement >

3

【譯文】10 ASP.NET Performance and Scalability Secrets

  < add address = " * "  maxconnection = " 100 "   />

4

【譯文】10 ASP.NET Performance and Scalability Secrets

  </ connectionManagement >

5

【譯文】10 ASP.NET Performance and Scalability Secrets

</ system.net >

6

【譯文】10 ASP.NET Performance and Scalability Secrets

7

【譯文】10 ASP.NET Performance and Scalability Secrets

預設為2,這太小了。這意味着同一個IP,你不能有兩個以上并發請求。擷取外部内容的站點,都因為這個預設設定而遭受堵塞。這裡,我設定成了100。如果你的web應用程式有很多的調用到一個指定的伺服器,你可以考慮将這個值設定的更高。

你可以從 "Improving ASP.NET Performance" 了解更多的配置。

上線之前,你必須要做的

如果你使用 ASP.NET 2.0 Membership Provider,你需要在上線之前,調整你的web.config:

  • 添加

    applicationname

     屬性在Profile Provider。如果你不他家一個指定的名字,Profile provider将會使用一個GUID。是以,在你的本地你将會由一個GUID,在應用程式伺服器上面,将會是另外一個GUID,如果你将本地的資料庫複制到伺服器上面,你将不能複用這個本地的資料庫,而且,ASPNET将會建立一個新的應用程式。這裡,你需要添加:

    1

    【譯文】10 ASP.NET Performance and Scalability Secrets

    < profile enabled = " true " >  

    2

    【譯文】10 ASP.NET Performance and Scalability Secrets

    < providers >  

    3

    【譯文】10 ASP.NET Performance and Scalability Secrets

    < clear  />  

    4

    【譯文】10 ASP.NET Performance and Scalability Secrets

    < add name = " ... "  type = " System.Web.Profile.SqlProfileProvider "  

    5

    【譯文】10 ASP.NET Performance and Scalability Secrets

    connectionStringName = " ... "  applicationName = " YourApplicationName "  

    6

    【譯文】10 ASP.NET Performance and Scalability Secrets

    description = " ... "   />  

    7

    【譯文】10 ASP.NET Performance and Scalability Secrets
    </ providers >
  • Profile provider将會自動儲存profile,當一個請求完成的時候。是以,這将導緻在資料庫上面一個不必要的更新,這需要一個很明顯的性能損耗。是以,關閉自動儲存,并且在你的代碼中顯示的調用

    Profile.Save();

    【譯文】10 ASP.NET Performance and Scalability Secrets
     Collapse
    <profile enabled="true" automaticSaveEnabled="false" >      
  • 角色管理器總是查詢資料庫,來獲得目前使用者的角色。這是非常大的性能損耗。你可以通過将其緩存到Cookie上,來避免這點。但是這點,需要你的使用者沒有被配置設定很多的角色,還沒有超出Cookie的2KB的限制。然而這并不是一個常見的情況。是以,你可安全的儲存的角色資訊到 Cookie 并且,在每一個*.aspx和 *.asmx的請求,節省一個資料庫請求。
    【譯文】10 ASP.NET Performance and Scalability Secrets
     Collapse
    <roleManager enabled="true" cacheRolesInCookie="true" >      

上面的設定,對于高容量的網站,一定要配置的。

Content Delivery Network 内容分發網絡

每一個請求,從浏覽器到你的伺服器,需要經過跨越全球的 Internet 主幹道。經過的國家,洲,海洋越多,通路的速度就越慢。舉個例子,如果你在美國有一個伺服器,有人在澳洲通路你的站點,每一個請求都要經過節點到達你的伺服器,然後用樣的從伺服器傳回到浏覽器。如果你的站點有一些比較大的靜态檔案,像圖檔,css,JavaScript;為他們發送的每一個請求和下載下傳他們,都花費大量的時間。如果你能在澳洲建立一個伺服器,并且将使用者重定向到澳洲的伺服器,然後每一個請求将會分數形式到達美國。不僅網絡延遲将會變小,而且,資料傳輸率将會變快,這樣,靜态的内容将會變的更快。如果你的站點有很多的靜态内容,這将會在使用者端帶來一個明顯的性能的改進。此外,ISP提供的國家之内的網絡連接配接比Internet更加快速,因為每一個國家隻有少量的介入Internet主幹道的接口,這個接口被所有的ISP共享。結果,擁有4M帶寬的使用者,将會在本國伺服器之間傳遞獲得全部的4M帶寬。但是在他們和國外的伺服器傳遞資料時,隻能獲得一個512KB的速度。這樣,在同一個國家内使用伺服器,能夠明顯的加快站點的下載下傳和相應速度。

除了改善站點的加載速度,CDN也能減少你的伺服器的負載。因為它處理靜态的可以緩存的内容,對于這些内容,你的伺服器将很少的獲到請求。這樣,到達你伺服器的請求明顯下降,可以節省你的伺服器資源來處理動态的請求。你的伺服器也可以省下很多的IIS日志,因為IIS不需要記錄靜态内容的請求了。如果你站點有很多的圖檔,CSS和JavaScript你每天可以節省上GB的伺服器空間。

【譯文】10 ASP.NET Performance and Scalability Secrets

上面的圖檔,顯示了www.pageflakes.com 在華盛頓特區的平均的響應時間,伺服器在達拉斯,德克薩斯。平均的響應時間在0.4秒。這個響應時間也包括了伺服器斷的執行時間。通常,他話費0.3-0.35秒來執行在伺服器的每一個頁面。是以,花費在網絡傳輸的時間大概在0.05秒或者50毫秒。這是非常快的,因為達拉斯和華盛頓之間隻有4到6個節點。

【譯文】10 ASP.NET Performance and Scalability Secrets

這幅圖顯示了從澳洲悉尼的平均響應時間。平均的響應時間是1.5秒,比華盛頓的時間多了很多。幾乎是美國的4倍。這裡有幾乎1.2秒的時間消耗在網絡傳輸上面。此外,有17到23個節點在悉尼和達拉斯之間。是以,站點在美國任何地點的下載下傳速度,至少是澳洲的倍被的速度。

内容傳遞網絡(content delivery network  - CDN)  是一個通過Internet 互聯的計算機系統。計算機互相透明合作,來向使用者傳遞内容(尤其是大的媒體内容)。CDN節點(指定地區的伺服器群集)被部署在多個地點,經常是跨越多個主幹道。這些節點互相協作,服務于将請求發往使用者。他們也透明的移動内容,在一個優化過的傳遞程序背後。CDN伺服器隻能的選擇最近的伺服器。它在你的計算機和最近的節點中查找最快的連接配接。不同國家的節點數量,連接配接到主幹道的數量,一個CDN測量它的強壯度。一些最流行的CDN有Akamai, Limelight, EdgeCast。Akamai被一些大公司使用,像Microsoft,Yahoo,AOL。這是相當貴的解決方案。但是Akamai擁有最好的性能,因為,他們幾乎在每一個比較好的城市都有他們的伺服器。然而,Akamai非常昂貴,而且他們隻接受能夠每月最少5K的客戶。對于一些小的公司,Edgecast是一個更加合算的解決方案。

【譯文】10 ASP.NET Performance and Scalability Secrets

這幅圖顯示了距離浏覽器端最近的CDN節點攔截了傳輸和伺服器響應。如果它在緩存中沒有響應,他用更加快速的路由而且比ISP提供的更加優化連接配接,從原始的伺服器獲得響應。如果這個響應已經被緩存,那麼,就可以接在CDN節點進行服務,而不用直接的通路原始伺服器。

大體上,有兩種CDN。一種是通過FTP上傳内容,你将會在他們的域名中獲得一個子域名,就像dropthings.somecdn.net.你将所有的靜态内容的URL從你自己的站點域名改成CDN的域名。是以,像/logo.gif 将會變成http://dropthings.somecdn.net/logo.gif.這非常好設定,但是仍舊有一些問題。你需要始終同步CDN上面的存儲的内容。部署就變得複雜,因為你需要同時上傳你的網站和CDN存儲。這樣的一個CDN(非常的便宜)是Cachefly.

一個更加友善的方法是儲存你自己站點的内容,但是使用域名domain aliasing. 你可以将你的内容儲存在一個子域名,這個子域名是指向你自己的域名的,就像static.dropthings.com. 然後,你用CNAME将這個子域名映射到CDN的 命名伺服器nameserver 像cache.somecdn.net.當一個浏覽器試着解析static.dropthigns.com, DNS檢查請求到CDN nameserver。然後nameserver 傳回一個距離你最近的CDN節點的IP,這樣,就給你一個最好的下載下傳性能。浏覽器發送請求到CDN節點。當CDN節點發現了這個請求,它會檢測這個内容是否已經被緩存了。如果已經被緩存了,就直接發送緩存的内容。如果沒有,一個請求将會到達你的伺服器,并且檢測在response中的cache。 它根據cache header,決定自己的緩存時間。同時,浏覽器不等待CDN節點擷取傳回内容。當CDN在更新緩存的時候,他就會路由請求到原始伺服器上面。有些時候,CDN扮演一個代理,攔截每一個請求,并且使用更快的路由和優化的連接配接,從原始伺服器獲得沒有緩存到的内容。這樣的一個CDN是Edgecast.

浏覽器上緩存AJAX調用

浏覽器可以緩存圖檔,JavaScript,CSS檔案到使用者的硬碟上,并且,他也能夠緩存XML HTTP 調用,如果這個調用是一個HTTP  GET.緩存是基于URL的,如果URL相同,并且,他已經被緩存在硬碟上了,這樣,應答(Response)就會從緩存中讀取,而不是在請求的時候,再一次通路伺服器。基本上,浏覽器可以緩存任何的HTTP GET 調用,并且傳回基于URL的緩存資料。如果你以HTTP GET的方式來獲得資料,并且伺服器傳回特殊的頭部(header),這通知浏覽器來緩存Response,在以後的調用中,Response将會立刻從緩存中傳回,這樣就節省了網絡延遲的時間以及下載下傳時間。

在PageFlakes,我們緩存了使用者的狀态,這樣,當使用者後面幾天再通路的時候,這個使用者立即得到浏覽器中緩存的頁面,并不是從伺服器上。這樣,第二次通路變得非常的快速。我們也将一些基于使用者動作而出現的頁面上小的部分緩存起來。當使用者做了相同的動作,一個被本地緩存的結果立刻被調用,這樣,節省了網絡互動時間。使用者獲得一個加載迅速,反映迅速的站點,速度增加的很明顯。

這個想法使得 通過Atlas WebService 的HTTP 

GET

 調用傳回指定的HTTPResponse 頭資訊,來告知浏覽器在在指定日期的長度緩存響應。如果你傳回Expires 頭資訊在相應過程中,浏覽器将會緩存這個XML HTTP Response。這裡有兩個頭資訊你需要傳回,來告知浏覽器緩存這個相應:

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

HTTP/1.1 200 OK 

Expires: Fri, 1 Jan 2030 

Cache-Control: public      

這個訓示浏覽器将這個相應緩存到2030,隻要你有相同的帶有相同參數的XML HTTP調用,你将會從你的電腦上獲得緩存内容,而不是在原始的伺服器。還有一些進階的方法來進一步的控制相應緩存。幾個例子,這裡有一個header 訓示了浏覽器緩存60秒,但是在60秒之後,聯系伺服器來獲得緩存的更新。這樣當緩存過期了,也保護了緩存的代理。

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

HTTP/1.1 200 OK 

Cache-Control: private, must-revalidate, proxy-revalidate, max-age=60      

讓我們試一試制作這樣的相應,在一個ASP.NET Web Service調用中:

1

【譯文】10 ASP.NET Performance and Scalability Secrets

[WebMethod][ScriptMethod(UseHttpGet = true )]

2

【譯文】10 ASP.NET Performance and Scalability Secrets

public   string  CachedGet()

3

【譯文】10 ASP.NET Performance and Scalability Secrets

{

4

【譯文】10 ASP.NET Performance and Scalability Secrets

 TimeSpan cacheDuration = TimeSpan.FromMinutes(1);

5

【譯文】10 ASP.NET Performance and Scalability Secrets

 Context.Response.Cache.SetCacheability(HttpCacheability.Public);

6

【譯文】10 ASP.NET Performance and Scalability Secrets

 Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));

7

【譯文】10 ASP.NET Performance and Scalability Secrets

 Context.Response.Cache.SetMaxAge(cacheDuration);

8

【譯文】10 ASP.NET Performance and Scalability Secrets

 Context.Response.Cache.AppendCacheExtension(

9

【譯文】10 ASP.NET Performance and Scalability Secrets

 "must-revalidate, proxy-revalidate");

10

【譯文】10 ASP.NET Performance and Scalability Secrets

11

【譯文】10 ASP.NET Performance and Scalability Secrets

 return DateTime.Now.ToString();

12

【譯文】10 ASP.NET Performance and Scalability Secrets

}

13

【譯文】10 ASP.NET Performance and Scalability Secrets

這将會傳回一下的Response頭:

【譯文】10 ASP.NET Performance and Scalability Secrets

這個Expires頭設定恰當了,但是問題是Cache-control。這裡顯示的max-age被設定為0.這樣就阻止了浏覽器做任何的緩存。如果你想禁止緩存,那麼可以使用這樣的一個cache-control頭。相反的事情發生了。

輸出是不正确的,沒有緩存:

【譯文】10 ASP.NET Performance and Scalability Secrets

這裡有一個ASP.NET2.0的bug,你不能改變max-age頭。由于max-age被設定成了0,ASP.NET 2.0 設定Cache-Control私有,因為,max-age = 0,意味着不需要緩存。這樣,就沒有方法使得ASP.NET 2.0 傳回恰當的頭,來緩存應答。這是由于,ASP.NET AJAX framework 攔截了WebService 的調用,并且錯誤的設定了max-age為預設的0,在執行一個Request之前。

Hack 的時間了,在反編譯HttpCachePolicy類的代碼之後(Context.Response.Cache 對象),我發現下面的代碼:

【譯文】10 ASP.NET Performance and Scalability Secrets

不知為什麼,this._maxAge 将被設定為0,并卻檢測"

if (!this._isMaxAgeSet || (delta < this._maxAge))

" 來防止被設定為更大的值。由于這個問題,我們需要跳過SetMaxAge方法,直接的設定_maxAge,使用反射。

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

[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();

}      

這将會傳回下面的頭資訊:

【譯文】10 ASP.NET Performance and Scalability Secrets

現在,max-age被設定成了60,這樣,浏覽器将會緩存這個相應60秒。如果你在60秒之内有相同的調用,那麼将會傳回相同的應答。這裡一個測試的輸出,顯示了從伺服器傳回的時間:

【譯文】10 ASP.NET Performance and Scalability Secrets

在一分鐘之後,緩存過期,并且浏覽器重新通路伺服器。用戶端的代碼如下:

1

【譯文】10 ASP.NET Performance and Scalability Secrets

function testCache()

2

【譯文】10 ASP.NET Performance and Scalability Secrets

{

3

【譯文】10 ASP.NET Performance and Scalability Secrets

 TestService.CachedGet(function(result)

4

【譯文】10 ASP.NET Performance and Scalability Secrets

 {

5

【譯文】10 ASP.NET Performance and Scalability Secrets

 debug.trace(result);

6

【譯文】10 ASP.NET Performance and Scalability Secrets

 });

7

【譯文】10 ASP.NET Performance and Scalability Secrets

}

有另外一個問題需要解決。在web.config中,你将會看到,ASP.NET ajax 将會添加:

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

<system.web>

<trust level="Medium"/>      

這将會阻礙我們來設定_maxAge字段,因為需要使用反射,是以,你需要删除這個信任級别,或者設定為Full。

1

【譯文】10 ASP.NET Performance and Scalability Secrets

< system.web >  

2

【譯文】10 ASP.NET Performance and Scalability Secrets

< trust level = " Full " />

最佳地利用浏覽器緩存

使用不變的URL

浏覽器緩存内容是根據URL的。當URL改變了,浏覽器從原始的伺服器中獲得新的版本。URL可以通過QueryString而改變。舉個例子,/default.aspx在浏覽器中緩存。如果請求/default.aspx?123 這将會從伺服器中擷取新版本。從伺服器新傳回的内容也會被緩存,如果有一個正确的緩存頭(header)的話。在這種情況下,改變queryString為/default.aspx?456将會從伺服器傳回一個新的内容。是以,如果你想獲得緩存的響應,那麼你就要随處保證使用不變的URL。在首頁,如果你請求了一個檔案/welcome.gif,確定在其他的頁面中,那麼使用相同的URL通路相同的檔案。一個常見的問題是,有些時候在URL中省略掉“www”子域名。www.pageflakes.com/default.aspx 和PageFlakes.com/default.aspx 并不相同。這兩個會分開緩存。

長時間的緩存靜态内容

靜态檔案可以緩存更長的時間,像一個月。如果你認為,你應該僅僅緩存一兩天,這樣,當你改變檔案的内容,使用者将會更快的獲得,你錯了。如果你想更新一個檔案,已經通過Expires頭資訊緩存過了,新使用者将會立刻就會看到這個新檔案,而老使用者則會一直要到浏覽器緩存過期。這樣,隻要你是用了Expires頭,那麼你應該盡可能的為其設定一個較大值。

舉個例子,如果你設定了Expires頭資訊來緩存一個檔案3天,一個使用者今天獲得這個檔案,并且後面的3天中,将其緩存。另外一個使用者明天獲得這個檔案,将會從明天開始的3開後,緩存。如果你後天修改了這個檔案,第一個使用者将會在第四天看到這個檔案,第二個使用者将會在第五天看到這個檔案。這樣,不同的使用者将會看到不用的版本。結果,設定一個較小的值并不能起到使得使用者盡快看到更新的作用。你應該改變這個檔案的URL來保證每一個使用者立刻獲得準确的資料。

你可以設定Expires 頭資訊在IIS的管理器中。你将會在後面的部分了解到怎麼設定。

使用緩存友好的目錄結構

存儲被緩存的内容在一個公用的檔案夾中。舉個例子,儲存你站點的所有的圖檔在/static目錄,而不是将圖檔分散的放在不同的子目錄。這樣将會幫助你獲得一個不變的URL,引文你在所有的地方都可以使用/static/images/somefile.gif.繼而,我們将會了解到,當你将靜态可以緩存的檔案放在一個共有的根目錄下,将其轉移到内容分發網絡(CDN)上面變的更加容易。

複用公用的圖檔檔案

有些時候,我們将公用的圖檔檔案放在一寫虛拟目錄下面,這樣,我們能夠書寫更見短小的路徑。舉一個例子,說你由一個indicator.gif檔案,分别放在根目錄,一些子目錄以及CSS的目錄。你這樣做是因為,你就不用但是相對路徑的問題了。這對緩存并沒有幫助。每一個檔案的副本,都在浏覽器中單獨被緩存。這樣你需要收集解決方案的所有的圖檔檔案,在除去副本之後,将他們放在放在一個相同的根目錄下面,在頁面和CSS檔案中使用相同的URL。

當你想使緩存過期的時候,修改檔案的名字

當你想改變一個靜态檔案,不要僅僅更新這個檔案,因為這個檔案已經在緩存在客戶的浏覽器中了。你需要改變檔案的名字,并且更新所有地方的引用,這樣,浏覽器将會下載下傳新的檔案。你可以将檔案的名字儲存在資料庫中或者配置檔案中,并且使用資料綁定動态的生成URL。這個方法,你能夠修改一個地方,立刻獲得整個站點的URL更新。

使用一個版本号,當通路一個靜态檔案

如果你不想 用一個檔案的多個副本而使得講台檔案目錄混雜,你能使用QueryString來區分相同檔案的不同版本。舉個例子,一個GIF能夠通過一個虛假的QueryString來通路到,像,/static/images/indicator.gif?v=1.當你改變了indicator.gif檔案,你可以覆寫掉同名檔案,并且将所有的引用修改為/static/images/indicator.gif?v=2.這個方法,你能夠重複的相同的檔案,而僅僅修改通路圖檔的引用。

将可緩存檔案放置在另外的一個域名

将靜态的内容放在一個不同的域名,始終是一個很好的主意。首先,浏覽器能夠打開兩個并發聯結來下載下傳靜态檔案。另外一點好處,你不需要發送Cookie到靜态檔案。當你把将靜态檔案放在和你的應用程式相同的域名下,浏覽器發送所有的ASP.NET cookie 和你的應用程式裡面的其他的Cookie。這就會請求的頭變的不必要的大,進而浪費帶寬。你不需要發送這些Cookie來通路靜态檔案。是以将檔案儲存在www.staticcontent.com 域名中當你的網站運作在www.dropthings.com 這個域名下。另外的域名不需要一個完全不同的網站,他僅僅是一個化名,并且共享web 應用程式路徑。

不會被緩存,是以,減少使用SSL

基于SSL的任何的内容,都不回被緩存、是以,你需要将一些靜态的内同放出SSL。另外,你應該試着限制SSL僅僅在一些安全的頁面,像登入頁面以及支付頁面。其餘的内容都應該在SSL之外。SSL加密了請求和響應,并且這樣就有另外的伺服器的負載。加密的内容也比原始的内容更大,這樣将會占用更大的帶寬。

HTTP POST 的請求從來不能被緩存

Cache僅僅能夠發生在HTTP GET 的請求。HTTP POST 請求不會被請求。是以,任何的你想要緩存的AJAX 調用,需要HTTP GET 開啟。(譯者:WebService 的 get 開啟)

生成 Content-Length 響應頭

當你通過 webservice 調用或者 HTTPHandler 來動态的輸出内容,確定你添加了Content-length頭。當通過Content-length獲得需要下載下傳的大小時,浏覽器會對下載下傳内容進行優化,擷取更快的速度。浏覽器能夠更加有效地使用持續的連接配接,當這個頭資訊出現。這将會使得浏覽器不會為每一個請求都開啟另外一個連接配接。當沒有 content-length頭,浏覽器不知道從伺服器獲得多少資料,這樣隻要從伺服器擷取内同,就保持這個連接配接打開直到連接配接關閉。是以,你失去了持續連接配接的益處,持續連接配接可以大大的減少下載下傳一些小檔案的速度,像CSS,JavaScript,和圖檔。

怎樣在IIS中設定緩存靜态内容

在IIS管理器,網站屬性對話框,有HTTP 頭頁籤,這裡你可以設定所有的請求的Expires 頭資訊。這裡,你可以設定是否立刻就過期還是在一定天數之内或者指定的日期過期。第二個選項(Expires after),使用了滑動過期,不會絕對過期。這非常有效,因為這是針對的每一個請求。無論什麼時候,某人請求一個靜态檔案,IIS将會基于這個設定來計算這個過期時間。

【譯文】10 ASP.NET Performance and Scalability Secrets

對于動态的頁面,ASPNET控制的,一個handler可以修改expire 頭而覆寫掉IIS 的預設設定。

按需加載UI,獲得一個更流暢的體驗

AJAX 網站都加載盡可能多的特性到浏覽器,而不帶有任何的PostBack。如果你看到像Pageflakes的起始頁,它僅僅一個單獨的頁面,不通過任何PostBack,給你提供所有的特性。一個快速但是“髒”地方法就是在頁面加載時,将每一個可能的HTML标簽都放進一個隐藏的div,當需要的時候,将這些隐藏的div顯示出來。但是這使得第一次加載的時候,速度太慢了,而且,浏覽器對于大量的dom操作,性能也會下降。是以,一個更好的方式是當需要的時候加載HTML。在我的dropthings 項目中,我已經為這個做出了一個例子。

【譯文】10 ASP.NET Performance and Scalability Secrets

當你點選 “help”連結,它将會動态加載幫助的内容。這些HTML并不會default.aspx的第一次呈現的時候産生。這樣,大量的HTML以及相關圖檔并沒有影響到網站的負載性能。他僅僅在使用者點選了”help“這個連結之後進行加載。另外,它将會被浏覽器緩存,這樣,僅僅需要加載一次。當使用者再一次點選連結之後,僅僅從浏覽器的緩存中獲得資料,而不是從原始伺服器中獲得資料。

這裡的原理是,生成一個調用*.aspx的XMLHTTP調用,獲得相應的HTML,将這些相應的HTML放到一個DIV容器裡面,并且使這個div可見。

AJAX架構有一個 Sys.Net.WebRequest 類,它能夠使你進行一個标準的HTTP 調用。你可以定義這個HTTP 方法,位址,頭資訊,以及調用的正文。這是一種底層的通過XMLHTTP的直接調用。一旦你構造了一個 web request,你可以使用Sys.Net.XMLHttpExecutor 來執行。

1

【譯文】10 ASP.NET Performance and Scalability Secrets

function showHelp()

2

【譯文】10 ASP.NET Performance and Scalability Secrets

{

3

【譯文】10 ASP.NET Performance and Scalability Secrets

 var request = new Sys.Net.WebRequest();

4

【譯文】10 ASP.NET Performance and Scalability Secrets

 request.set_httpVerb("GET");

5

【譯文】10 ASP.NET Performance and Scalability Secrets

 request.set_url('help.aspx');

6

【譯文】10 ASP.NET Performance and Scalability Secrets

 request.add_completed( function( executor )

7

【譯文】10 ASP.NET Performance and Scalability Secrets

 {

8

【譯文】10 ASP.NET Performance and Scalability Secrets

 if (executor.get_responseAvailable()) 

9

【譯文】10 ASP.NET Performance and Scalability Secrets

 {

10

【譯文】10 ASP.NET Performance and Scalability Secrets

11

【譯文】10 ASP.NET Performance and Scalability Secrets

 var helpDiv = $get('HelpDiv');

12

【譯文】10 ASP.NET Performance and Scalability Secrets

 var helpLink = $get('HelpLink');

13

【譯文】10 ASP.NET Performance and Scalability Secrets

 var helpLinkBounds = Sys.UI.DomElement.getBounds(helpLink);

14

【譯文】10 ASP.NET Performance and Scalability Secrets

15

【譯文】10 ASP.NET Performance and Scalability Secrets

 helpDiv.style.top = (helpLinkBounds.y + helpLinkBounds.height) + "px";

16

【譯文】10 ASP.NET Performance and Scalability Secrets

 var content = executor.get_responseData();

17

【譯文】10 ASP.NET Performance and Scalability Secrets

 helpDiv.innerHTML = content;

18

【譯文】10 ASP.NET Performance and Scalability Secrets

 helpDiv.style.display = "block"; 

19

【譯文】10 ASP.NET Performance and Scalability Secrets

20

【譯文】10 ASP.NET Performance and Scalability Secrets

 }

21

【譯文】10 ASP.NET Performance and Scalability Secrets

 });

22

【譯文】10 ASP.NET Performance and Scalability Secrets

23

【譯文】10 ASP.NET Performance and Scalability Secrets

 var executor = new Sys.Net.XMLHttpExecutor();

24

【譯文】10 ASP.NET Performance and Scalability Secrets

 request.set_executor(executor); 

25

【譯文】10 ASP.NET Performance and Scalability Secrets

 executor.executeRequest();

26

【譯文】10 ASP.NET Performance and Scalability Secrets

}

這個例子顯示了幫助子產品的資訊,通過通路help.aspx來加載,并且将其被容注入到Helpdiv中。這個應答能通過在help.aspx裡面設定 output cache directive 節點而被緩存。這樣,下一次當使用者再一次點選這個連結的時候,UI将會立刻的顯示出來。help.aspx沒有<html>僅僅是在div裡面的内容。

1

【譯文】10 ASP.NET Performance and Scalability Secrets

<% @ Page Language = " C# "  AutoEventWireup = " true "  CodeFile = " Help.aspx.cs "  

2

【譯文】10 ASP.NET Performance and Scalability Secrets

 Inherits = " Help "   %>

3

【譯文】10 ASP.NET Performance and Scalability Secrets

<% @ OutputCache Location = " ServerAndClient "  Duration = " 604800 "  VaryByParam = " none "   %>

4

【譯文】10 ASP.NET Performance and Scalability Secrets

< div  class = " helpContent " >

5

【譯文】10 ASP.NET Performance and Scalability Secrets

< div id = " lipsum " >

6

【譯文】10 ASP.NET Performance and Scalability Secrets

< p >

7

【譯文】10 ASP.NET Performance and Scalability Secrets

8

【譯文】10 ASP.NET Performance and Scalability Secrets
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis lorem

eros, volutpat sit amet, venenatis vitae, condimentum at, dolor. Nunc

porttitor eleifend tellus. Praesent vitae neque ut mi rutrum cursus.      

使用這個方法,你能夠将你的使用者界面分割到一個個小的aspx檔案中,盡管這些aspx檔案不能有JavaScript或者樣式表子產品,他們能夠包含你需要顯示的大量的HTML。這樣,你就能保持最初的下載下傳足夠的小,僅僅加載最基本的東西。當使用者使用新的功能的時候,動态加載這些區域。

優化 ASP.NET 2.0 Profile Provider

你知道有兩個重要的存儲過程在ASPNET2.0的Profile Provider中,你能顯著的優化?如果你沒有足夠的優化,你的伺服器在大負載的情況下将會停止你的業務。這裡有一個故事:

在3月,Pageflakes 在mix2006上面展示。回來之後,我們有了一個迷人的時間。我們在Showcase of Atlas Web site 作為第一個公司展示。每天的通路量上升的非常的快。一天,我們通知,我們的資料庫伺服器不夠用了。我們重新開機了伺服器,恢複了,然而一個小時候,又死掉了。在做對還留在伺服器中的,當機時候的情況過很多的分析之後,我們發現,100% cpu使用率,還有相當高的IO操作。硬碟過熱,為了自我保護,硬碟自動關閉。這讓我們非常的奇怪,因為,我們認為我們非常聰明,并且,我們已經測試過每一個WebService方法。是以,我們在上百兆的日志中尋找,以便尋找出哪個WebService 方法占用了時間。我們猜測是一個,它是第一個函數,用來讀取使用者的頁面設定。我們将這個方法分割成小部分,以便找到哪個部分占用了大部分時間。

1

【譯文】10 ASP.NET Performance and Scalability Secrets

private  GetPageflake( string  source,  string  pageID,  string  userUniqueName)

2

【譯文】10 ASP.NET Performance and Scalability Secrets

{

3

【譯文】10 ASP.NET Performance and Scalability Secrets

  if ( Profile.IsAnonymous ) 

4

【譯文】10 ASP.NET Performance and Scalability Secrets

 {

5

【譯文】10 ASP.NET Performance and Scalability Secrets

  using  ( new  TimedLog(Profile.UserName, " GetPageflake " ))

6

【譯文】10 ASP.NET Performance and Scalability Secrets

  {

你看,整個方法都被計時了。如果你想知道這個計時是怎樣工作的,我将會在另外一篇文章中說明。我們也在我們懷疑的更小的部分添加了計時。但是我們沒能夠找到任何的在我們的代碼中耗時的部分。我們的資料庫始終都是被優化的(畢竟,你知道睡在檢查他,是我)。

同時,使用者在大叫,管理人員在尖叫,支援人員在電話上被抱怨。開發者證狂流汗,他們額頭上的血管也清晰可見了。沒有什麼特别的,僅僅是一個我們每一個月都要面對的典型狀況。

現在,你一定會喊,“你可以使用SQL Profiler,白癡!”。我們正在使用SQL Server workgroup 版,這并沒有SQL profiler。這樣,我們必須設法讓其運作在運作在伺服器上。不要問是如何做到的。在運作了SQL Profiler之後,我們震驚了。帶給我們如此大痛苦的這個光榮的存儲過程是

dbo.aspnet_Profile_GetProfiles

!

我們廣泛的使用(并且仍在使用) Profile provider.

這是這個存儲過程:

CREATE PROCEDURE [dbo].[aspnet_Profile_GetProfiles]

@ApplicationName nvarchar(256),

@ProfileAuthOptions int,

@PageIndex  int,

@PageSize   int,

@UserNameToMatch   nvarchar(256) = NULL,

@InactiveSinceDate datetime = NULL

AS

BEGIN

DECLARE @ApplicationId uniqueidentifier

SELECT @ApplicationId = NULL

SELECT @ApplicationId = ApplicationId

FROM aspnet_Applications

WHERE LOWER(@ApplicationName)

= LoweredApplicationName



IF (@ApplicationId IS NULL)

RETURN



-- Set the page bounds

DECLARE @PageLowerBound int

DECLARE @PageUpperBound int

DECLARE @TotalRecords   int

SET @PageLowerBound = @PageSize * @PageIndex

SET @PageUpperBound = @PageSize - 1 + @PageLowerBound



-- Create a temp table TO store the select results

CREATE TABLE #PageIndexForUsers

(

IndexId int IDENTITY (0, 1) NOT NULL,

UserId uniqueidentifier

)



-- Insert into our temp table

INSERT INTO #PageIndexForUsers (UserId)



SELECT u.UserId 

FROM    dbo.aspnet_Users

u, dbo.aspnet_Profile p 

WHERE   ApplicationId = @ApplicationId 

AND u.UserId = p.UserId       

AND (@InactiveSinceDate

IS NULL OR LastActivityDate

<= @InactiveSinceDate)

AND (   

(@ProfileAuthOptions = 2)

OR (@ProfileAuthOptions = 0 

AND IsAnonymous = 1)

OR (@ProfileAuthOptions = 1 

AND IsAnonymous = 0)

)

AND (@UserNameToMatch

IS NULL OR LoweredUserName

LIKE LOWER(@UserNameToMatch))

ORDER BY UserName



SELECT u.UserName, u.IsAnonymous, u.LastActivityDate,

p.LastUpdatedDate, DATALENGTH(p.PropertyNames)

+ DATALENGTH(p.PropertyValuesString) 

+ DATALENGTH(p.PropertyValuesBinary)

FROM    dbo.aspnet_Users

u, dbo.aspnet_Profile p, #PageIndexForUsers i

WHERE   

u.UserId = p.UserId 

AND p.UserId = i.UserId 

AND i.IndexId >= @PageLowerBound 

AND i.IndexId <= @PageUpperBound



DROP TABLE #PageIndexForUsers



END

END       

首先,它查找 ApplicationID

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

DECLARE @ApplicationId  uniqueidentifier



SELECT @ApplicationId = NULL



SELECT @ApplicationId = ApplicationId FROM aspnet_Applications

WHERE LOWER(@ApplicationName) = LoweredApplicationName



IF (@ApplicationId IS NULL) 

RETURN      

然後建立臨時表,以便存儲使用者資訊。

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

-- Create a temp table TO store the select results

CREATE TABLE #PageIndexForUsers

(

IndexId int IDENTITY (0, 1) NOT NULL,

UserId uniqueidentifier

)

-- Insert into our temp table

INSERT INTO #PageIndexForUsers (UserId)      

如果它被調用的非常頻繁,那麼将會産生非常高的IO因為臨時表的建立。他也穿過了兩個大表

aspnet_Users 和

aspnet_Profile

。存儲過程的這種寫法,如果一個使用者有多個個人資訊,将會傳回所有的資訊。但是通常,我們僅僅為每一個使用者存儲一個個人資訊。是以,就沒必要建立臨時表了。另外,沒有必要做 LIKE 

LOWER(@UserNameToMatch)

這樣的操作。我們總是存儲使用者的全名,直接的滿足相等的條件。

是以,我們打開存儲過程,并且将其改為直接通路:

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

IF @UserNameToMatch IS NOT NULL 

BEGIN

SELECT u.UserName, u.IsAnonymous, u.LastActivityDate, p.LastUpdatedDate,

DATALENGTH(p.PropertyNames)

+ DATALENGTH(p.PropertyValuesString) + DATALENGTH(p.PropertyValuesBinary)

FROM    dbo.aspnet_Users u

INNER JOIN dbo.aspnet_Profile p ON u.UserId = p.UserId

WHERE u.LoweredUserName = LOWER(@UserNameToMatch)



SELECT @@ROWCOUNT

END



ELSE

BEGIN -- Do the original bad things      

在本地,它運作正常。現在,需要将其運作在伺服器上面。這是一個非常重要的存儲過程,被ASPNET 2.0的Profile Provider使用,ASPNET的核心。如果我們做錯了些什麼,我們也許不能立刻就發現這個錯誤,但是也許在一個月之後,我們發現了,使用者的Profile混淆了,這就沒有辦法補救了。是以,這是一個非常難的決定,将這個運作在一個上線中的項目,而且沒有足夠的測試。我們沒有足夠的時間來做測試了。我們已經挂掉了。是以,我們聚集在一起,一起祈禱,并且在SQL Server Management Studio中按下了“Excute”按鈕。

這個存儲過程,運作良好。在伺服器上面,我們發現CPU從100%降到30%。IO使用降到了40%。

我們重新活了!

這是另外一個存儲過程,在每一個頁面,WebService都會被調用,因為我們使用Profile Provider 非常廣泛。

REATE PROCEDURE [dbo].[aspnet_Profile_GetProperties]

@ApplicationName   nvarchar(256),

@UserName  nvarchar(256),

@CurrentTimeUtc  datetime



AS

BEGIN

DECLARE @ApplicationId uniqueidentifier

SELECT @ApplicationId = NULL

SELECT @ApplicationId = ApplicationId 

FROM dbo.aspnet_Applications 

WHERE LOWER(@ApplicationName) = LoweredApplicationName



IF (@ApplicationId IS NULL)

RETURN



DECLARE @UserId uniqueidentifier

SELECT @UserId = NULL



SELECT @UserId = UserId

FROM   dbo.aspnet_Users

WHERE ApplicationId = @ApplicationId 

AND LoweredUserName = 

LOWER(@UserName)

IF (@UserId IS NULL)

RETURN



SELECT TOP 1 PropertyNames, PropertyValuesString, PropertyValuesBinary

FROM         dbo.aspnet_Profile

WHERE        UserId = @UserId



IF (@@ROWCOUNT > 0)

BEGIN

UPDATE dbo.aspnet_Users

SET    [email protected]

WHERE UserId = @UserId

END



END      

當你運作這個存儲過程,看這個統計:

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

Table 'aspnet_Applications'. Scan count 1, logical reads 2, physical reads 0, 

read-ahead reads 0, lob logical reads 0, lob physical

reads 0, lob read-ahead reads 0.

(1 row(s) affected)

Table 'aspnet_Users'. Scan count 1, logical reads 4, physical reads 0, 

read-ahead reads 0, lob logical reads 0, lob physical

reads 0, lob read-ahead reads 0.



(1 row(s) affected)

(1 row(s) affected)

Table 'aspnet_Profile'. Scan count 0, logical reads 3, physical reads 0, 

read-ahead reads 0, lob logical reads 0, lob physical

reads 0, lob read-ahead reads 0.

(1 row(s) affected)

Table 'aspnet_Users'. Scan count 0, logical reads 27, physical reads 0, 

read-ahead reads 0, lob logical reads 0, lob physical

reads 0, lob read-ahead reads 0.

(1 row(s) affected)

(1 row(s) affected)      

這個存儲過程支援了Profile對象的所有定制屬性,當Profile對象在一次請求中第一次被通路的時候。

首先,它做了一個查詢,在

aspnet_application

來通過Application name 找到目前的Application id。你可以輕松的替換掉這個,通過寫死的形式在存儲過程中指出這個Application id 來節省工作。通常我們僅僅是運作一個應用程式,是以,沒有必要在每一個次調用中,都查詢Application id。這是一個快速的優化,然而,從用戶端統計,你能夠看到,哪裡是性能的瓶頸:

【譯文】10 ASP.NET Performance and Scalability Secrets

然後,看最後的一個子產品,

aspnet_users

表更新了

LastActivityDate

字段。這是一個最費開銷的操作。

【譯文】10 ASP.NET Performance and Scalability Secrets

這個用來確定Profile Provider 知道使用者的Profile 什麼時候最後被通路的。但是我們不需要做這個在包含通路Profile的每一個單獨的頁面加載或者web service的調用。也許,我們能夠做這個在使用者的第一個登入,或者使用者退出。在我們的情況中,當使用者在頁面時,很多的web service被請求。無論如何,也僅僅有一個頁面。是以,我們能夠輕松的删除這個,為了節省在每一個請求中,更新巨大的

aspnet_users

表。

怎樣才在不使站點挂掉的情況下查詢 ASP.NET 2.0 Membership 表

這樣的查詢在你的開發環境中運作的很好:

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

Select * from aspnet_users where UserName = 'blabla'      

或者,你可以毫無問題的獲得一些使用者的資訊,使用:

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

Select * from aspnet_profile where userID = '…...'      

你甚至可以這樣更新

aspnet_membership

 表:

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

Update aspnet_membership 

SET Email = '[email protected]' 

Where Email = '…'      

但是,當你的産品伺服器上面有一個巨大的資料庫,運作上面的任何的都會是你的伺服器挂掉。原因是盡管這些查詢看起來是我們頻繁的使用的,但是沒有一個是帶索引的。是以,以上的結果在“表查詢”(最差的查詢)在各有數百萬條資料的表中。

我就是對于我們來說,發生了什麼。我們使用這樣的字段,像

UserName

Email

UserID

IsAnonymous

 等等的,在Pageflakes很多的市場報表中。這些報表僅僅是市場團隊使用,其他人并不使用。現在站點運作良好,但是一天會有幾次市場團隊和使用者叫我們,尖叫:“網站變慢了!”,“使用者報表非常慢!”,“有一些頁面逾時了!”等等。通常,當他們叫我們,我們會告訴他們,“等等,現在看看”并且,我們徹底的檢測站點。我們使用SQL Profiler 來看到底什麼有問題。但是我們什麼都沒有找到。Profiler 顯示的查詢運作良好。CPU負載在正常範圍内。站點運作良好和流暢。我們在電話中告訴他們“我們沒有看到任何問題,怎麼了?”

那麼,為什麼當我們像試着調查出這個問題的時候,我們沒有發現任何的緩慢?但是站點有時候确實變的非常慢,在我們沒有進行調查的某些時候。

市場團隊每天運作像上面的查詢幾次。當他們運作任何的這些查詢,由于這些部分沒有在索引中,會使得IO以及CPU變的非常高-像這樣:

【譯文】10 ASP.NET Performance and Scalability Secrets

我們使用的是SCSI 硬碟,15000RPM。非常貴,也非常快。CPU是64位的Dual core Dual Xeon 。這兩個硬體在各自的領域都是非常強勁的。但是像這樣子的查詢仍舊會使得我們挂掉,由于由一個巨大的資料庫。

但是,當市場團隊給我們打電話并且我們保持連線找到問題的時候,從來不會發生。因為他們再給我們打電話,他們沒有運作任何的可以使伺服器挂掉的報表查詢。他們也在站點的其他的部分工作,和大部分投訴的使用者做着一樣的事情。

讓我們來看看索引:

Table: aspnet_users

  • Clustered Index = 

    ApplicationID

    LoweredUserName

  • NonClustered Index = 

    ApplicationID

    LastActivityDate

  • Primary Key = 

    UserID

Table: aspnet_membership

  • Clustered Index = 

    ApplicationID

    LoweredEmail

  • NonClustered = 

    UserID

Table: aspnet_Profile

  • Clustered Index = 

    UserID

大部分索引都包含ApplicationID 在裡面。除非你使用ApplicationID = “…”在where子句中,它将不會使用任何的索引。結果就是所有的查詢都會遭受一個整表查詢。将ApplicationID放到where子句中(在aspnet_Applicaiont表中找到ApplicationID),這樣,所有的查詢都會變得非常非常快。

不要使用

Emai

 或者 

UserName

字段在where子句中。他們不是索引的一部分,而應該使用

LoweredUserName

LoweredEmail 

字段,聯合使用

ApplicationID 

字段。所有的查詢一定要有

ApplicationID 

WHERE 

子句中。

我們的管理站點,包含了一些這樣的查詢,結果,每當市場團隊試着生成報表,他們就占用了所有的CPU和硬碟資源,其他的站點就變得相當慢,有些時候會出現沒有響應的情況。

確定你的查詢的where子句中始終包含了索引列。否則,當你上線了,你一定會受到懲罰。

防止拒絕服務(DOS)攻擊

Web服務是最吸引黑客的目标,因為甚至一個剛上學前班的黑客都能都過重複的調用 消耗量大的web 服務是一個伺服器挂掉。像 Pageflakes這樣的Ajax的起始頁,是運作這樣的dos攻擊的最佳目标,因為,如果你在不儲存Cookie的情況下,重複的通路首頁,每一個的通路都會建立一個新的使用者,一個新的頁面設定,新的widgets,或者諸如此類的. 第一次通路,是開銷最大的一次。盡管如此,他是最容發現的一個使得站點挂掉。你能自己試一下,就是僅僅這樣簡單的代碼:

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

for( int i = 0; i < 100000; i ++ )

{

WebClient client = new WebClient();

client.DownloadString("http://www.pageflakes.com/default.aspx");

}      

讓你非常吃驚的,你将會注意到,在一系列的調用之後,你不能獲得一個有效地響應。這并不是你成功的是這個站點挂掉了,而是你的請求被拒絕了。你高興與你不能獲得任何的服務了,這樣,你活着了拒絕服務(對于你自己)。我們非常高興的拒絕了你的服務。Deny You of Service (DYOS).

這裡我是用的方法是使用一種開銷并不大的方式來記住多少個請求來自同一個IP。當這個數字超過了入口,拒絕後面的服務一段時間。這個方式要記住調用這個IP在ASPNET的Cache,并且,将一定每一個IP的請求數量存入其中。當這個數字超過預定的界限,拒絕後面的服務一段時間,像10分鐘。在10分鐘後,重新允許這個IP的請求。

我有一個類叫做 

ActionValidator

 ,它包含了第一次通路,重複通路,匿名PostBack。添加新的子產品,添加新的頁面等等的數量。他會檢查,是否這個動作的數量超過了沒有。

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

public static class ActionValidator

{

private const int DURATION = 10; // 10 min period



public enum ActionTypeEnum

{

FirstVisit = 100, // The most expensive one, choose the value wisely. 

ReVisit = 1000,  // Welcome to revisit as many times as user likes

Postback = 5000,    // Not must of a problem for us

AddNewWidget = 100, 

AddNewPage = 100,

}      

這個枚舉包含了各種動作的類型,來檢測在一個指定時間段-10分鐘的操作數量。

一個靜态方法叫做

IsValid

 來進行檢測。如果沒有超過請求的限制,它将會傳回true,否則就傳回false。一旦,你獲得一個false,那麼,你就可以調用Request.End()(譯者:應該是Response.End()吧。。)使得ASPNET不用進行下一步的處理。或者,你也将其轉到一個頁面,并且顯示“祝賀,你的拒絕服務攻擊成功了!”

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

public static bool IsValid( ActionTypeEnum actionType )

{

HttpContext context = HttpContext.Current;

if( context.Request.Browser.Crawler ) return false;



string key = actionType.ToString() + context.Request.UserHostAddress;

var hit = (HitInfo)(context.Cache[key] ?? new HitInfo());



if( hit.Hits > (int)actionType ) return false;

else hit.Hits ++;



if( hit.Hits == 1 )

context.Cache.Add(key, hit, null, DateTime.Now.AddMinutes(DURATION), 

System.Web.Caching.Cache.NoSlidingExpiration, 

System.Web.Caching.CacheItemPriority.Normal, null);

return true;

}      

緩存的key由動作類型已經用戶端ip共同構成。首先,它檢查目前動作,目前ip,是否有任何的記錄,如果沒有記錄,開始計數并且在這個時間段内的記錄到緩存中。緩存對象的絕對日期保證了在這個時間段後,資料将會被清除,并别重新開始記錄。如果當在緩存中已經有相應的資料的時候,檢測最後的點選次數,判斷是否超過限制。如果沒有超過限制,增加這個計數器,不用再一次的将更改過的資料存儲在緩存中通過:

Cache[url]=hit

;因為hit對象是一個引用,修改它,就意味着也在緩存中修改了,事實上,如果你将其重新的放進緩存中,緩存過期時間将會重新計算,這樣,你的計算邏輯就失敗了。

這個方法的使用非常簡單,在default.aspx:

【譯文】10 ASP.NET Performance and Scalability Secrets

 Collapse

protected override void OnInit(EventArgs e)

{

base.OnInit(e);



// Check if revisit is valid or not

if( !base.IsPostBack ) 

{

// Block cookie less visit attempts

if( Profile.IsFirstVisit )

{

if( !ActionValidator.IsValid(ActionValidator.ActionTypeEnum.FirstVisit))  

Response.End();

}

else

{

if( !ActionValidator.IsValid(ActionValidator.ActionTypeEnum.ReVisit) )  

Response.End();

}

}

else

{

// Limit number of postbacks

if( !ActionValidator.IsValid(ActionValidator.ActionTypeEnum.Postback) ) 

Response.End();

}

}      

這裡,我檢測各種動作,像第一個通路,重複通路,PostBack等。

當然,你可以使用一些思科的防火牆,來防止DOS攻擊。你将會從你的伺服器提供商那裡獲得保證,他們的整個網絡都是DOS或者DDOS(分布式DOS)攻擊免疫的。他們保證的是網絡級的攻擊,像TCP SYN 攻擊,或者malformed packet floods 等等。沒有辦法調查的包,找到不帶Cookie的加載站點過多次,或者添加太多的widgets的特定的ip。這叫做應用程式級的攻擊,沒有硬體的保護。這就必須在你的代碼中實作。

很少的站點使用了應用程式級的方法來預防DOS攻擊。這樣,通過寫一個簡單的循環,連續不斷的點選開銷大的頁面或者是web service,很輕松的使伺服器挂掉。我希望這個簡單而有效的類能夠幫助你來防止DOS攻擊。

結論

你現在已經了解了一些方法将ASPNET發揮到極限,在相同的硬體配置情況下,來獲得更好的性能。你也了解到了一些周遊的AJAX技術使得你的網站負載以及通路速度上升。最後,你了解到了怎麼樣抵禦大量的通路,以及把靜态内容通過内容分發網絡來傳遞,以抵抗巨大的流量要求。所有的這些技術能使你的站點加載的更加快速,感覺更加流暢,在一個低花費的情況下獲得更高的流量。你可以進一步的了解到 改進ASPNET和ASPNET AJAX的性能的資訊在我的書裡 

from http://blog.ncuhome.cn/llj098/logs/2008/12/3/25270.html

原文 :http://www.codeproject.com/KB/aspnet/10ASPNetPerformance.aspx

繼續閱讀