通過避免下列 10 個常見 ASP.NET 缺陷使網站平穩運行[msdn]

來源:互聯網
上載者:User
通過避免下列 10 個常見 ASP.NET 缺陷使網站平穩運行發布日期: 2006-07-11 | 更新日期: 2006-07-11

Jeff Prosise

本文將討論:

緩衝和 Forms 身分識別驗證

檢視狀態和工作階段狀態

設定檔屬性序列化

線程池飽和

類比和設定設定檔

本文使用了下列技術:

.NET Framework、ASP.NET、Windows Server 2003

本頁內容
LoadControl 和輸出緩衝
會話和輸出緩衝
Forms 身分識別驗證票證生存期
檢視狀態:無聲的效能殺手
SQL Server 工作階段狀態:另一個效能殺手
未緩衝的角色
設定檔屬性序列化
線程池飽和
類比和 ACL 授權
不要完全信賴它 — 請設定資料庫的設定檔!
結論

ASP.NET 成功的其中一個原因在於它降低了 Web 開發人員的門檻。即便您不是電腦科學博士也可以編寫 ASP.NET 代碼。我在工作中遇到的許多 ASP.NET 開發人員都是自學成材的,他們在編寫 C# 或 Visual Basic 之前都在編寫 Microsoft Excel 試算表。現在,他們在編寫 Web 應用程式,總的來說,他們所做的工作值得表揚。

但是與能力隨之而來的還有責任,即使是經驗豐富的 ASP.NET 開發人員也難免會出錯。在多年的 ASP.NET 項目諮詢工作中,我發現某些錯誤特別容易導致缺陷不斷髮生。其中某些錯誤會影響效能。其他錯誤會抑制延展性。有些錯誤還會使Team Dev耗費寶貴的時間來跟蹤錯誤和意外的行為。

下面是會導致 ASP.NET 生產應用程式的發布過程中出現問題的 10 個缺陷以及可避免它們的方法。所有樣本均來自我對真實的公司構建真實的 Web 應用程式的親身體驗,在某些情況下,我會通過介紹 ASP.NET Team Dev在開發過程中遇到的一些問題來提供相關的背景。

LoadControl 和輸出緩衝

極少有不使用使用者控制項的 ASP.NET 應用程式。在出現主版頁面之前,開發人員使用使用者控制項來提取公用內容,如頁首和頁尾。即使在 ASP.NET 2.0 中,使用者控制項也提供了有效方法來封裝內容和行為以及將頁面分為多個地區,這些地區的緩衝能力可以獨立於作為整體的頁面進行控制(一種稱為段緩衝的特殊輸出緩衝形式)。

使用者控制項可以採用聲明的方式載入,也可以強制載入。強制載入依賴於 Page.LoadControl,它執行個體化使用者控制項並返回控制項引用。如果使用者控制項包含自訂類型的成員(例如,公用屬性),則您可以轉換該引用並從您的代碼訪問自訂成員。圖 1 中的使用者控制項實現名為 BackColor 的屬性。以下代碼載入使用者控制項並向 BackColor 分配一個值:

protected void Page_Load(object sender, EventArgs e){// 載入使用者控制項並將其添加到頁面中Control control = LoadControl("~/MyUserControl.ascx");PlaceHolder1.Controls.Add(control);// 設定其背景色((MyUserControl)control).BackColor = Color.Yellow;}

以上代碼實際上很簡單,但卻是一個等待粗心的開發人員掉進去的陷阱。您能找出其中的破綻嗎?

如果您猜到該問題與輸出緩衝有關,那麼您是正確的。正如您所看到的一樣,上述程式碼範例編譯和運行都正常,但是如果嘗試將以下語句(完全合法)添加到 MyUserControl.ascx 中:

<%@ OutputCache Duration="5" VaryByParam="None" %>

則當您下一次運行該頁面時,您將看到 InvalidCastException (oh joy!) 和以下錯誤訊息:

“無法將類型為‘System.Web.UI.PartialCachingControl’的對象轉換為類型‘MyUserControl’。”

因此,此代碼在沒有 OutputCache 指令時運行正常,但如果添加了 OutputCache 指令就會出錯。ASP.NET 不應該以這種方式運行。頁面(和控制項)對於輸出緩衝應該是不可知的。那麼,這代表什麼意思?

問題在於為使用者控制項啟用輸出緩衝時,LoadControl 不再返回對控制項執行個體的引用;相反,它返回對 PartialCachingControl 執行個體的引用,而 PartialCachingControl 可能會也可能不會封裝控制項執行個體,具體取決於控制項的輸出是否被緩衝。因此,如果開發人員調用 LoadControl 以動態載入使用者控制項並且為了訪問控制項特定的方法和屬性而轉換控制項引用,他們必須注意進行該操作的方式,以便不管是否具有 OutputCache 指令,代碼都可以運行。

圖 2 說明動態載入使用者控制項以及轉換返回的控制項引用的正確方法。以下是其工作原理概要:

如果 ASCX 檔案缺少 OutputCache 指令,則 LoadControl 返回一個 MyUserControl 引用。Page_Load 將該引用轉換為 MyUserControl 並設定控制項的 BackColor 屬性。

如果 ASCX 檔案包括一個 OutputCache 指令並且控制項的輸出沒有被緩衝,則 LoadControl 返回一個對 PartialCachingControl 的引用,此 PartialCachingControl 的 CachedControl 屬性包含對基礎 MyUserControl 的引用。Page_Load 將 PartialCachingControl.CachedControl 轉換為 MyUserControl 並設定該控制項的 BackColor 屬性。

如果 ASCX 檔案包括一個 OutputCache 指令並且控制項的輸出被緩衝,則 LoadControl 返回一個對 PartialCachingControl(其 CachedControl 屬性為空白)的引用。注意,Page_Load 不再繼續執行操作。無法設定控制項的 BackColor 屬性,因為該控制項的輸出來源於輸出緩衝。換句話說,根本沒有要設定屬性的 MyUserControl。

不管 .ascx 檔案中是否具有 OutputCache 指令,圖 2中的代碼都將運行。雖然看起來複雜一點,但它會避免煩人的錯誤。簡單並不總是代表易於維護。

返回頁首

會話和輸出緩衝

談到輸出緩衝,ASP.NET 1.1 和 ASP.NET 2.0 都存在一個潛在的問題,該問題會影響在 Windows Server 2003 和 IIS 6.0 上啟動並執行伺服器中的輸出快取頁面。我曾經親眼看到該問題在 ASP.NET 生產伺服器中出現過兩次,這兩次都是通過關閉輸出緩衝來解決的。後來我瞭解到有一個比禁用輸出緩衝更好的解決方案。以下是我第一次遇到該問題時的情況。

當時的情況是這樣的,某個網站(我們在此稱為 Contoso.com,它在小型 ASP.NET Web 領域中運行公用電子商務應用程式)與我的團隊聯絡,抱怨他們遇到了“跨線程”錯誤。使用 Contoso.com 網站的客戶常常突然丟失已經輸入的資料,但卻看到另一使用者的相關資料。稍做分析即發現,跨線程這個描述並不準確;“跨會話”錯誤更為貼切。看起來 Contoso.com 是在工作階段狀態中儲存資料的,由於某些原因,使用者會偶爾隨機地串連到其他使用者的會話。

我的一個團隊成員編寫了一個診斷工具,用來將每個 HTTP 要求和響應的關鍵要素(包括 Cookie 標題)記錄到日誌中。然後,他將該工具安裝在 Contoso.com 的 Web 服務器上,並讓其運行了幾天。結果非常明顯。大概每 100000 個請求中會發生一次這樣的情況:ASP.NET 正確地為全新會話分配一個會話 ID 並返回 Set-Cookie 標題中的會話 ID。然後,它會在下一個緊相鄰的請求中返回相同的會話 ID(即,相同的 Set-Cookie 標題),即使該請求已經與一個有效會話相關聯並且正確提交了 Cookie 中的會話 ID。實際上,ASP.NET 是隨機將使用者從他們自己的會話中切換出去並將他們串連到其他會話。

我們很驚訝,於是開始尋找原因。我們首先檢查了 Contoso.com 的原始碼,讓我們感到欣慰的是,問題不在那。接著,為了確保問題與應用程式宿主在 Web 領域無關,我們只保留一個伺服器在運行,而關閉了所有其他伺服器。問題仍然存在,這並不意外,因為我們的日誌顯示匹配的 Set-Cookie 標題絕不會來自兩個不同的伺服器。ASP.NET 意外地產生了重複的會話 ID,這令人難以置信,因為它使用 .NET Framework RNGCryptoServiceProvider 類產生這些 ID,並且會話 ID 的長度足以確保相同的 ID 決不會產生兩次(至少在下一個萬億年內不會產生兩次)。除此之外,即使 RNGCryptoServiceProvider 錯誤地產生了重複的隨機數字,也無法解釋 ASP.NET 為何不可思議地將有效會話 ID 替換為新的 ID(不唯一)。

憑直覺,我們決定看一下輸出緩衝。當 OutputCacheModule 緩衝 HTTP 響應時,它必須小心不要緩衝了 Set-Cookie 標題;否則,包含新會話 ID 的緩衝響應會將緩衝響應的所有接收者(以及其請求產生了緩衝響應的使用者)串連到同一會話。我們檢查了原始碼;Contoso.com 在兩個頁面中啟用了輸出緩衝。我們關閉了輸出緩衝。結果,應用程式運行數天而沒有發生一個跨會話問題。此後,它運行了兩年多都沒有發生任何錯誤。在具有不同應用程式和一組不同 Web 服務器的另一家公司中,我們看到完全相同的問題也消失了。就像在 Contoso.com 一樣,消除輸出緩衝就能解決問題。

Microsoft 後來確認此行為源於 OutputCacheModule 中的問題。(當您閱讀本文時,可能已經發布了更新。)當 ASP.NET 與 IIS 6.0 一起使用並且啟用核心模式緩衝時,OutputCacheModule 有時無法從它傳遞給 Http.sys 的緩衝響應中刪除 Set-Cookie 標題。下面是導致出現錯誤的特定事件順序:

最近沒有訪問網站(因此也沒有對應的會話)的使用者請求一個啟用了輸出緩衝的頁面,但是其輸出當前在緩衝中不可用。

該請求執行用於訪問使用者最新建立的會話的代碼,從而導致會話 ID Cookie 在響應的 Set-Cookie 標題中返回。

OutputCacheModule 向 Http.sys 提供輸出,但是無法從響應中刪除 Set-Cookie 標題。

Http.sys 在後續的請求中返回緩衝響應,誤將其他使用者串連到會話。

故事的寓意又是什麼呢?工作階段狀態和核心模式輸出緩衝不能混合使用。如果您在啟用輸出緩衝的頁中使用工作階段狀態,並且應用程式在 IIS 6.0 上運行,則您需要關閉核心模式輸出緩衝。您仍將受益於輸出緩衝,但是因為核心模式輸出緩衝比普通輸出緩衝快得多,所以緩衝不會同樣有效。有關此問題的詳細資料,請參見 support.microsoft.com/kb/917072。

您可以通過在頁面的 OutputCache 指令中包含 VaryByParam="*" 屬性來關閉單個頁面的核心模式輸出緩衝,雖然這樣做可能導致記憶體需求驟增。另一種更安全的方法是通過在 web.config 中包含下列元素來關閉整個應用程式的核心模式緩衝:

<httpRuntime enableKernelOutputCache="false" />

您還可以使用註冊表設定來全域性地禁用核心模式輸出緩衝,即禁用全部伺服器的核心模式輸出緩衝。有關詳細資料,請參見 support.microsoft.com/kb/820129。

每次我聽到客戶報告會話發生了費解的問題,我都會詢問他們是否在任何頁面中使用了輸出緩衝。如果確實使用了輸出緩衝,並且宿主作業系統是 Windows Server 2003,我會建議他們禁用核心模式輸出緩衝。問題通常就會迎刃而解。如果問題沒有解決,則錯誤存在於代碼中。警惕!

返回頁首

Forms 身分識別驗證票證生存期

您能找出以下代碼的問題嗎?

FormsAuthentication.RedirectFromLoginPage(username, true);

此代碼看似沒有問題,但決不能在 ASP.NET 1.x 應用程式中使用,除非應用程式中其他位置的代碼抵消了此語句的負面作用。如果您不能確定原因,請繼續閱讀。

FormsAuthentication.RedirectFromLoginPage 執行兩個任務。首先,當 FormsAuthenticationModule 將使用者重新導向到登入頁時,FormsAuthentication.RedirectFromLoginPage 將使用者重新導向到他們原來請求的頁面。其次,它發布一個身分識別驗證票證(通常攜帶在 Cookie 中,而且在 ASP.NET 1.x 中總是攜帶在 Cookie 中),這個票證允許使用者在預定的一段時間內保持已經過身分識別驗證狀態。

問題就在於這個時間段。在 ASP.NET 1.x 中,向 RedirectFromLoginPage 傳遞另一個為 false 的參數會發出一個臨時身分識別驗證票證,該票證預設情況下在 30 分鐘之後到期。(您可以使用 web.config 的 元素中的 Timeout 屬性來更改逾時期限。)然而,傳遞另一個為 true 的參數則會發出一個永久身分識別驗證票證,其有效期間為 50 年!這樣就會發生問題,因為如果有人竊取了該身分識別驗證票證,他們就可以在票證的有效期間內使用受害者的身份訪問網站。竊取身分識別驗證票證有多種方法 — 在公用無線訪問點探測未加密的通訊、跨網站編寫指令碼、以物理方式訪問受害者的電腦等等 — 因此,向 RedirectFromLoginPage 傳遞 true 比禁用您的網站的安全性好不了多少。幸運的是,此問題已經在 ASP.NET 2.0 中得到瞭解決。現在的 RedirectFromLoginPage 以相同的方式接受在 web.config 中為臨時和永久身分識別驗證票證指定的逾時。

一種解決方案是決不在 ASP.NET 1.x 應用程式的 RedirectFromLoginPage 的第二個參數中傳遞 true。但是這不切實際,因為登入頁的特點通常是包含一個“將我保持為登入狀態”框,使用者可以選中該框以收到永久而不是臨時身分識別驗證 Cookie。另一種解決方案是使用 Global.asax(如果您願意的話,也可以使用 HTTP 模組)中的程式碼片段,此程式碼片段會在包含永久身分識別驗證票證的 Cookie 返回瀏覽器之前對其進行修改。

圖 3 包含一個這樣的程式碼片段。如果此程式碼片段位於 Global.asax 中,它會修改傳出永久 Forms 身分識別驗證 Cookie 的 Expires 屬性,以使 Cookie 在 24 小時後到期。通過修改注釋為“新的到期日期”的行,您可以將逾時設定為您喜歡的任何日期。

您可能會覺得奇怪,Application_EndRequest 方法調用本地 Helper 方法 (GetCookieFromResponse) 來檢查身分識別驗證 Cookie 的傳出響應。Helper 方法是解決 ASP.NET 1.1 中另一個錯誤的方法,如果您使用 HttpCookieCollection 的字串索引產生器來檢查不存在的 Cookie,此錯誤會導致虛假 Cookie 添加到響應中。使用整數索引產生器作為 GetCookieFromResponse 可以解決該問題。

返回頁首

檢視狀態:無聲的效能殺手

從某種意義上說,檢視狀態是有史以來最偉大的事情。畢竟,檢視狀態使得頁面和控制項能夠在回傳之間保持狀態。因此,您不必像在傳統的 ASP 中那樣編寫代碼,以防止在單擊按鈕時文字框中的文本消失,或在回傳後重新查詢資料庫和重新綁定 DataGrid。

但是檢視狀態也有缺點:當它增長得過大時,它便成為一個無聲的效能殺手。某些控制項(例如文字框)會根據檢視狀態作出相應判斷。其他控制項(特別是 DataGrid 和 GridView)則根據顯示的資訊量確定檢視狀態。如果 GridView 顯示 200 或 300 行資料,我會望而生畏。即使 ASP.NET 2.0 檢視狀態大致是 ASP.NET 1 x 檢視狀態的一半大小,一個糟糕的 GridView 也可以容易地將瀏覽器和 Web 服務器之間的串連的有效頻寬減少 50% 或更多。

您可以通過將 EnableViewState 設定為 false 來關閉單個控制項的檢視狀態,但某些控制項(特別是 DataGrid)在不能使用檢視狀態時會失去某些功能。控制檢視狀態的更佳解決方案是將其保留在伺服器上。在 ASP.NET 1.x 中,您可以重寫頁面的 LoadPageStateFromPersistenceMedium 和 SavePageStateToPersistenceMedium 方法並按您喜歡的方式處理檢視狀態。圖 4 中的代碼顯示的重寫可防止檢視狀態保留在隱藏欄位中,而將其保留在工作階段狀態中。當與預設工作階段狀態進程模型一起使用時(即,工作階段狀態儲存在記憶體中的 ASP.NET 輔助進程中時),在工作階段狀態中儲存檢視狀態尤其有效。相反,如果工作階段狀態儲存在資料庫中,則只有測試才能顯示在工作階段狀態中保留檢視狀態會提高還是降低效能。

在 ASP.NET 2.0 中使用相同的方法,但是 ASP.NET 2.0 能夠提供更簡單的方法將檢視狀態保留在工作階段狀態中。首先,定義一個自訂頁適配器,其 GetStatePersister 方法返回 .NET Framework SessionPageStatePersister 類的一個執行個體:

public class SessionPageStateAdapter :System.Web.UI.Adapters.PageAdapter{public override PageStatePersister GetStatePersister (){return new SessionPageStatePersister(this.Page);}}

然後,通過將 App.browsers 檔案按以下方式放入應用程式的 App_Browsers 檔案夾,將自訂頁適配器註冊為預設頁適配器:

<browsers><browser refID="Default"><controlAdapters><adapter controlType="System.Web.UI.Page"adapterType="SessionPageStateAdapter" /></controlAdapters></browser></browsers>

(您可以將檔案命名為您喜歡的任何名稱,只要它的副檔名為 .browsers 即可。)此後,ASP.NET 將載入頁適配器並使用返回的 SessionPageStatePersister 以保留所有頁面狀態,包括檢視狀態。

使用自訂頁適配器的一個缺點是它全域性地作用於應用程式中的每一頁。如果您更願意將其中一些頁面的檢視狀態保留在工作階段狀態中而不保留其他頁面的檢視狀態,請使用圖 4 中顯示的方法。另外,如果使用者在同一會話中建立多個瀏覽器視窗,您使用該方法可能會遇到問題。

返回頁首

SQL Server 工作階段狀態:另一個效能殺手

ASP.NET 使得在資料庫中儲存工作階段狀態變得簡單:只需切換 web.config 中的開關,工作階段狀態就會輕鬆地移動到後端資料庫。對於在 Web 領域中啟動並執行應用程式來說,這是一項重要功能,因為它允許該領域中的每個伺服器共用工作階段狀態的一個公用庫。添加的資料庫活動降低了單個請求的效能,但是延展性的提高彌補了效能的損失。

這看起來都還不錯,但是您略微考慮一下下列幾點,情況就會有所不同:

即使在使用工作階段狀態的應用程式中,大多數頁也不使用工作階段狀態。

預設情況下,ASP.NET 工作階段狀態管理器對每個請求中的會話資料存放區執行兩個訪問(一個讀取存取和一個寫入訪問),而不管請求的頁是否使用工作階段狀態。

換句話說,當您使用 SQL Server 工作階段狀態選項時,您在每個請求中都要付出代價(兩個資料庫訪問)— 甚至在與工作階段狀態無關的頁面的請求中。這會直接對整個網站的輸送量造成負面影響。

圖 5 消除不必要的工作階段狀態資料庫訪問

那麼您應該怎麼辦呢?很簡單:禁用不使用工作階段狀態的頁中的工作階段狀態。這樣做總是一個好辦法,但是當工作階段狀態儲存在資料庫中時,該方法尤其重要。圖 5 顯示如何禁用工作階段狀態。如果頁面根本不使用工作階段狀態,請在其 Page 指令中包含 EnableSessionState="false",如下所示:

<%@ Page EnableSessionState="false" ... %>

該指令阻止工作階段狀態管理器在每個請求中讀取和寫入工作階段狀態資料庫。如果頁面從工作階段狀態中讀取資料,但卻不寫入資料(即,不修改使用者會話的內容),則將 EnableSessionState 設定為 ReadOnly,如下所示:

<%@ Page EnableSessionState="ReadOnly" ... %>

最後,如果頁面需要對工作階段狀態進行讀/寫訪問,則省略 EnableSessionState 屬性或將其設定為 true:

<%@ Page EnableSessionState="true" ... %>

通過以這種方式控制工作階段狀態,可以確保 ASP.NET 只在真正需要時才訪問工作階段狀態資料庫。消除不必要的資料庫訪問是構建高效能應用程式的第一步。

順便說一下,EnableSessionState 屬性是公開的。該屬性自 ASP.NET 1.0 以來就已經進行了說明,但是我至今仍很少見到開發人員利用該屬性。也許是因為它對於記憶體中的預設工作階段狀態模型並不十分重要。但是它對於 SQL Server 模型卻很重要。

返回頁首

未緩衝的角色

以下語句經常出現於 ASP.NET 2.0 應用程式的 web.config 檔案以及介紹 ASP.NET 2.0 角色管理器的樣本中:

<roleManager enabled="true" />

但正如以上所示,該語句確實會對效能產生明顯的負面影響。您知道為什麼嗎?

預設情況下,ASP.NET 2.0 角色管理器不會緩衝角色資料。相反,它會在每次需要確定使用者屬於哪個角色(如果有)時參考角色資料存放區。這意味著一旦使用者經過了身分識別驗證,任何利用角色資料的頁(例如,使用啟用了安全裁減設定的網站圖的頁,以及使用 web.config 中基於角色的 URL 指令進行訪問受到限制的頁)將導致角色管理器查詢角色資料存放區。如果角色儲存在資料庫中,那麼對於每個請求需要訪問多個資料庫的情況,您可以輕鬆地免除訪問多個資料庫。解決方案是配置角色管理器以在 Cookie 中緩衝角色資料:

<roleManager enabled="true" cacheRolesInCookie="true" />

您可以使用其他<roleManager> 屬性控制角色 Cookie 的特徵 — 例如,Cookie 應保持有效期限(以及角色管理器因此返回角色資料庫的頻率)。角色 Cookie 預設情況下是經過簽名和加密的,因此安全風險雖然不為零,但也有所緩解。

返回頁首

設定檔屬性序列化

ASP.NET 2.0 設定檔服務為保持每個使用者的狀態(例如個人化喜好設定和語言喜好設定)的問題提供了一個現成的解決方案。要使用設定檔服務,您可以定義一個 XML 設定檔,其中包含要保留的代表單個使用者的屬性。然後,ASP.NET 編譯一個包含相同屬性的類,並通過添加到頁的設定檔屬性提供對類執行個體的強型別訪問。

設定檔靈活性很強,它甚至允許將自訂資料類型用作設定檔屬性。但是,其中卻存在一個問題,我親眼看到該問題導致開發人員出差錯。圖 6 包含一個名為 Posts 的簡單類,以及將 Posts 用作設定檔屬性的設定檔定義。但是,該類和該設定檔在運行時會產生意外的行為。您能找出其中的原因嗎?

問題在於 Posts 包含一個名為 _count 的私人欄位,該欄位必須進行序列化和還原序列化,才能完全凍結和重新凍結類執行個體。但是 _count 卻沒有經過序列化和還原序列化,因為它是私人的,而且預設情況下 ASP.NET 設定檔管理員使用 XML 序列化對自訂類型進行序列化和還原序列化。XML 序列化程式將忽略非公用成員。因此,會對 Posts 的執行個體進行序列化和還原序列化,但是每次還原序列化類執行個體時,_count 都會重設為 0。

一種解決方案是使 _count 成為公用欄位而非私人欄位。另一種解決方案是使用公用讀取/寫屬性封裝 _count。最佳解決方案是將 Posts 標記為可序列化(使用 SerializableAttribute),並將設定檔管理員配置為使用 .NET Framework 二進位序列化程式對類執行個體進行序列化和還原序列化。該解決方案能夠保持類本身的設計。與 XML 序列化程式不同的是,二進位序列化程式序列化欄位,而不管是否可以訪問。圖 7 顯示 Posts 類的修複版本並反白了更改的附帶設定檔定義。

您應該牢記的一點是,如果您使用自訂資料類型作為設定檔屬性,並且該資料類型具有必須序列化才能完全序列化類別型執行個體的非公用資料成員,則在屬性聲明中使用 serializeAs="Binary" 屬性並確保類型本身是可序列化的。否則,將無法進行完整的序列化,並且您還將浪費時間來嘗試確定設定檔無法工作的原因。

返回頁首

線程池飽和

在執行資料庫查詢並等待 15 秒或更長時間來獲得返回的查詢結果時,我經常對看到的實際的 ASP.NET 頁數感到非常驚訝。(我也等待了 15 分鐘才看到查詢結果!)有時,延遲是由於返回的資料量很大而導致的不可避免的無奈結果;而有時,延遲則是由於資料庫的設計不佳導致的。但不管是什麼原因,長時間的資料庫查詢或任何類型的長時間 I/O 操作在 ASP.NET 應用程式中都會導致輸送量的下降。

關於這個問題我以前已經詳細地描述過,所以在此就不再作過多的說明了。我只說一點就夠了,ASP.NET 依賴於有限的線程池處理請求,如果所有線程都被佔用來等待資料庫查詢、Web 服務調用或其他 I/O 操作完成,則在某個操作完成並且釋放出一個線程之前,其他請求都必須排隊等待。當請求排隊時,效能會急劇下降。如果隊列已滿,則 ASP.NET 會使隨後的請求失敗並出現 HTTP 503 錯誤。這種情況不是我們希望在 Web 生產伺服器的生產應用程式上所樂見的。

解決方案非非同步頁面莫屬,這是 ASP.NET 2.0 中最佳卻鮮為人知的功能之一。對非同步頁面的請求從一個線程上開始,但是當它開始一個 I/O 操作時,它將返回該線程以及 ASP.NET 的 IAsyncResult 介面。操作完成後,請求通過 IAsyncResult 通知 ASP.NET,ASP.NET 從池中提取另一個線程並完成對請求的處理。值得注意的是,當 I/O 操作發生時,沒有佔用線程池線程。這樣可以通過阻止其他頁面(不執行較長的 I/O 操作的頁面)的請求在隊列中等待,從而顯著地提高輸送量。

您可以在 MSDNMagazine 的 2005 年 10 月刊中閱讀有關非同步頁面的所有資訊。I/O 綁定而不是電腦綁定且需要很長時間執行的任何頁面很有可能成為非同步頁面。

當我將關於非同步頁面的資訊告知開發人員時,他們經常回答“那真是太棒了,但是我的應用程式中並不需要它們。”對此我回答說:“你們的任何頁面需要查詢資料庫嗎?它們調用 Web 服務嗎?您是否已經檢查 ASP.NET 效能計數器中關於排隊請求和平均等待時間的統計資訊?即使您的應用程式至今運行正常,但是隨著您的客戶規模的增長,應用程式的負載可能會增加。”

實際上,絕大多數實際的 ASP.NET 應用程式都需要非同步頁面。請切記這一點!

返回頁首

類比和 ACL 授權

以下是一個簡單的配置指令,但是每當在 web.config 中看到它時都讓我眼前一亮:

<identity impersonate="true" />

此指令在 ASP.NET 應用程式中啟用用戶端類比。它將代表用戶端的存取權杖附加到處理請求的線程,以便作業系統執行的安全性檢查針對的是用戶端身份而不是輔助進程身份。ASP.NET 應用程式很少需要類比;我的經驗告訴我,開發人員通常都是由於錯誤的原因而啟用類比的。以下是原因所在。

開發人員經常在 ASP.NET 應用程式中啟用類比,以便可以使用檔案系統許可權來限制對頁面的訪問。如果 Bob 沒有查看 Salaries.aspx 的許可權,則開發人員將會啟用類比,以便可以通過將存取控制清單 (ACL) 設定為拒絕 Bob 的讀取許可權,阻止 Bob 查看 Salaries.aspx。但是存在以下隱患:對於 ACL 授權來說,類比是不必要的。在 ASP.NET 應用程式中啟用 Windows 身分識別驗證時,ASP.NET 會自動為請求的每個 .aspx 頁面檢查 ACL 並拒絕沒有讀取檔案許可權的調用者的請求。即使禁用了類比,它仍會這樣操作。

有的時候需要證明類比的合理性。但是您通常可以用良好的設計來避免它。例如,假定 Salaries.aspx 在資料庫中查詢只有管理員才能知道的工資資訊。通過類比,您可以使用資料庫許可權拒絕非管理員查詢工資資料的能力。或者您可以不考慮類比,並且通過為 Salaries.aspx 設定 ACL 以使非管理員不具有讀取許可權,從而限制對工資資料的訪問。後一種方法提供的效能更佳,因為它完全避免了類比。它也消除了不必要的資料庫訪問。為什麼查詢資料庫僅由於安全原因被拒絕?

順便說一下,我曾經協助對一個傳統的 ASP 應用程式進行故障排除,該應用程式由於記憶體佔用不受限制而定期重新啟動。一個沒有經驗的開發人員將目標 SELECT 語句轉換成了 SELECT *,而沒有考慮要查詢的表包含映像,這些映像很大而且數目很多。問題由於未檢測到記憶體流失而惡化。(我的Managed 程式碼領域!)多年來運行正常的應用程式開始突然停止工作,因為以前返回一兩KB資料的 SELECT 語句現在卻返回了幾MB。如果再加上不充分的版本控制,Team Dev的生活將不得不“亢奮起來”— 這裡所謂的“亢奮”,就如同當您在晚上要睡覺時,還不得不看著您的孩子玩令人厭煩的足球遊戲一樣。

理論上,傳統的記憶體流失不會發生在完全由Managed 程式碼組成的 ASP.NET 應用程式中。但是記憶體使用量量不足會通過強制垃圾收集更頻繁地發生而影響效能。即使是在 ASP.NET 應用程式中,也要警惕 SELECT *!

返回頁首

不要完全信賴它 — 請設定資料庫的設定檔!

作為一名顧問,我經常被詢問為何應用程式沒有按預期執行。最近,有人詢問我的團隊為何 ASP.NET 應用程式只完成請求文檔所需輸送量(每秒的請求數)的大約 1/100。我們以前所發現的問題是我們在不能正常啟動並執行 Web 應用程式中發現的問題特有的 — 和我們所有人應該認真對待的教訓。

我們運行 SQL Server Profiler 並監視此應用程式和後端的資料庫之間的互動情況。在一個更極端的案例中,僅僅只是一個按鈕單擊,就導致資料庫發生了 1,500 多個錯誤。您不能那樣構建高效能的應用程式。良好的體繫結構總是從良好的資料庫設計開始。不管您的代碼的效率有多高,如果它被編寫不佳的資料庫所拖累,就會不起作用。

糟糕的資料訪問體繫結構通常源於下面的一個或多個方面:

拙劣的資料庫設計(通常由開發人員設計,而不是資料庫管理員)。

DataSets 和 DataAdapters 的使用 — 尤其是 DataAdapter.Update,它適用於 Windows 表單應用程式和其他胖用戶端,但是對於 Web 應用程式來說通常不理想。

具有拙劣編製計算程式、以及執行相對簡單的操作需消耗很多 CPU 週期的設計糟糕的資料訪問層 (DAL)。

必須先確定問題才能對其進行處理。確定資料訪問問題的方式是運行 SQL Server Profiler 或等效的工具以查看後台正在執行的操作。檢查應用程式和資料庫之間的通訊之後,效能調整才完成。嘗試一下 — 您可能會對您的發現大吃一驚。

返回頁首

結論

現在您已經瞭解在產生 ASP.NET 生產應用程式過程中可能遇到的一些問題及其解決方案了。下一步是仔細查看您自己的代碼並嘗試避免我在此概述的一些問題。ASP.NET 可能降低了 Web 開發人員的門檻,但是您的應用程式完全有理由靈活、穩定和高效。請認真考慮,避免出現新手易犯的錯誤。

圖 8 提供了一個簡短檢查列表,您可以使用它來避免本文中描述的缺陷。您可以建立一個類似的安全缺陷檢查列表。例如:

您是否已經對包含敏感性資料的配置節進行加密?

您是否正在檢查並驗證在資料庫操作中使用的輸入,是否使用了 HTML編碼輸入作為輸出?

您的虛擬目錄中是否包含具有不受保護的副檔名的檔案?

如果您重視網站、承載網站的伺服器以及它們所依賴的後端資源的完整性,則這些問題非常重要。

Jeff Prosise 是對 MSDN Magazine 貢獻很大的編輯以及多本書籍的作者,這些書籍中包括 Programming Microsoft .NET (Microsoft Press, 2002)。他也是軟體諮詢和教育公司 Wintellect 的共同創始人。

摘自 MSDN Magazine 的 2006 年 7 月刊。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.