防止可淹沒應用程式的池溢出
大多數 ADO.NET 資料提供者使用串連池,以提高圍繞 Microsoft 中斷連線的 .NET 結構構建的應用程式的效能。應用程式首先開啟一個串連(或從串連池獲得一個串連控制代碼),接著運行一個或多個查詢,然後處理行集,最後將串連釋放回串連池。如果沒有串連池,這些應用程式將花費許多額外時間來開啟和關閉串連。
當您使用 ADO.NET 串連池來管理基於 Web 的應用程式和用戶端/伺服器 Web 服務應用程式的串連時,您的客戶通常會獲得更快的串連和更好的總體效能。但是,當您的應用程式或 Web 網站上突然湧入了同時希望進行串連的大量客戶時,會發生什麼事情呢。您的應用程式會“沉沒”,還是會“遊泳”。就像救生員一樣,您需要仔細監視串連池,以維護它的良好效能,並防止串連池發生溢出。我們首先探討串連池可能溢出的原因,然後討論如何編寫代碼或使用 Windows 效能監控器來監視串連池。
正如我於 2003 年 5 月發表的 Swimming in the .NET Connection Pool (InstantDoc ID 38356) 一文中討論的那樣,當您使用串連池時,您需要知道許多有關延展性和效能的詳細資料。請記住,您需要監視和管理兩個基本因素:每個池管理的串連數和串連池的數量。在一個有效生產系統中,池的數量通常很少(1 到 10),而且,使用中的串連的總數也很少(少於 12 )有效查詢只用不到一秒鐘的時間就可以完成,並中斷連線。因此,即使有數百個客戶同時訪問您的 Web 網站,相對較少的幾個串連常常足以處理整個負載。為了使您的應用程式有效地運行,您必須使串連資源處於自己的控制之下,並要監視池的狀態,這樣,在監視池發生溢出以及您的客戶開始抱怨(或離開您的網站)之前您會收到某種警告。
為什麼會發生串連池溢出。
參加電子郵件討論群組的人常常抱怨應用程式是如何在測試中是“龍”而在形成為產品時就變成了“蟲”的。有時,他們會報告說,當串連了大約 100 個用戶端時,應用程式會停止或掛起。請記住,一個池中的預設串連數是 100。如果您嘗試從池中開啟 100 個以上的串連,ADO.NET 會使應用程式的串連請求排隊等候,直到有閒置串連。應用程式(及其使用者)將這種情況視為進入 Web 頁的延遲或視為應用程式死結。讓我們首先討論一下這個問題是如何產生的。
在 ADO.NET 中,SqlClient .NET 資料提供者為您提供了兩種開啟和管理串連的方法。首先,當您需要手工管理串連時,可以使用 DataReader 對象。利用這種方法,您的代碼將構造一個 SqlConnection 對象,設定 ConnectionString 屬性,然後使用 Open 方法來開啟串連。當程式碼完成 DataReader 後,您要在 SqlConnection 對象停止作用之前關閉 SqlConnection。要處理行集,您可以將 DataReader 傳遞到應用程式中的另一個常式,但仍然需要確保 DataReader 及其串連處於關閉狀態。如果您不關閉 SqlConnection,代碼會“泄漏”每個操作的串連,於是串連池對串連進行累積,最後便發生溢出。與 ADO 和 Visual Basic (VB) 6.0 中的情況不同,.NET ***回收器不會為您關閉 SqlConnection 並進行清理。我稍後要討論的 清單 1 顯示了如何開啟串連和產生 DataReader 以從一個簡單的查詢返回行集,來向串連池施加壓力的。
您也可能在使用 DataAdapter 對象時遇到問題。DataAdapter Fill 和 Update 方法可自動開啟
用不到五個串連來處理每天的幾十萬次點擊。
標註 A 中的常式建立 SqlConnection 對象和 SqlCommand 對象,設定 CommandText,並開啟串連。然後,標註 B 中的代碼確定執行 DataReader 時是否使用 CommandBehavior.CloseConnection,這取決於使用者在 Web Form上選擇了哪些 CheckBox 控制項。
在標註 C 的代碼中,我指定是否將 DataReader 行集綁定到 DataGrid,或者是否在整個行集中進行迴圈。標註 C 的代碼測試當您到達通過 DataReader 從資料提供者傳遞迴來的行集的末尾時會發生什麼事情。
現在,我使用標註 D 中的代碼來指定是手工關閉串連還是讓某個其他動作(例如,資料繫結)來完成這項工作。坦白地說,以手工方式關閉串連通常是最安全的,因此,您可以肯定串連不會被孤立。
如果代碼成功地運行到這一步,說明我已經成功地開啟和關閉了 110 個串連。不過,如果出了問題,標註 E 的代碼中的例外處理常式會將異常(通常是 Timeout)作為 InvalidOperationException 捕獲,該異常是串連池已滿時 ADO.NET 的回應程式式。
表 1 匯總了各個選項使常式成功運行或失敗的方式。請注意,如果您不設定 CommandBehavior.CloseConnection 選項,您的操作最終會失敗 — 即使在使用繫結控制項的情況下也是如此。即使您使用該選項,但如果您沒有使用複雜的繫結控制項,或者沒有手工關閉 SqlDataAdapter 或 SqlConnection,該進程仍然會失敗。
當我結束了這些應用程式範例的運行後,我已經產生了 1000 多個以上的池串連 — 所有串連均處於孤立狀態。雖然“SQL Server 使用者串連”計數為 0,但留下大約 40 個串連池。在我重新引導系統之前,孤立的池不會消失。
我用於此測試的應用程式範例包括使用 DataAdapter 來返回行的常式。除非您手工管理串連,否則,DataAdapter 將正確地開啟和關閉 SqlConnection 對象,因此,您不太可能遇到孤立的池串連。不過,如果您的應用程式同時使用 DataReader 和 DataAdapter,您可能會發現,如果某個串連與一個未關閉的 DataReader 相關聯,則 DataAdapter 無法針對該串連執行查詢。
確定串連池何時達到最大串連數
正如我在 Swimming in the .NET Connection Pool 一文中討論的那樣,當串連池達到您通過 Max Pool Size ConnectionString 選項指定的最大串連數時,ADO.NET 將阻止任何隨後開啟額外串連的嘗試。如果某個串連在您在 ConnectionTimeout 選項中指定的時間之前變為可用,.NET 資料提供者將向您的應用程式傳遞一個指向該串連的指標,以便將控制項返回給應用程式。不過,如果沒有及時釋放任何串連,串連請求將引發 InvalidOperationException 異常。
現在您必須決定要採取的措施,我不建議您告訴使用者您已經用完了所有串連。有些應用程式會通知使用者系統正忙於協助其他客戶,並建議使用者稍後進行訪問。其他應用程式則播放一段動畫,通知使用者系統尚未死結,而是正在忙於處理他們的請求。同時,您的代碼重新嘗試操作。在所有情況下,您應該
記錄這些故障,以便協助診斷問題的癥結所在,並記錄您已經耗盡了資源。
監視串連池
您已經開啟和關閉了一個串連,現在您希望知道該串連是否仍然處於開啟狀態。您可以使用幾種方法來確定有多少串連仍然處於開啟狀態,以及它們正在執行何種操作:
| • |
運行 sp_who 或 sp_who2。這些系統預存程序從 sysprocess 系統資料表返回資訊,該系統資料表顯示所有背景工作處理序的狀態及其有關資訊。通常,您會看到每個串連有一個伺服器處理序 ID (SPID)。如果您是通過在連接字串中使用 Application Name 參數來命名您的串連的,那麼,您將很容易找到工作的串連。 |
| • |
使用帶有 SQLProfiler TSQL_Replay 模板的 SQL Server 事件探查器來跟蹤開啟的串連。如果您很熟悉事件探查器,此方法比通過使用 sp_who 進行輪詢要更容易。 使用效能監控器來監視池和串連。我稍後再討論此方法。
| • |
在代碼中監視效能計數器。您可以通過使用常式來提取計數器或通過使用新的 .NET PerformanceCounter 控制項來監視串連池的狀況和已建立的串連的數量。這兩種方法都包括在您可以從 http://www.sqlmag.com 進行下載的應用程式範例中。 現在我們將討論如何尋找串連池計數器,以及如何使用這些監視方法。 串連池計數器在哪裡。要監視串連池計數器,您必須監視 ADO.NET 在其中建立和增加這些計數器的系統。如果您從遠程系統進行串連,ADO.NET 並不總是在 Microsoft IIS 伺服器或 SQL Server 上建立池;它在 ADO.NET 代碼啟動並執行系統上建立池。此系統可以是運行 IIS、Web 應用程式或 Web 服務的遠程 Windows 或中介層系統。相反,SQL Server 效能計數器位於 SQL Server 系統上 — 而不是用戶端上。 使用效能監控器來監視池。如果您使用 Microsoft 管理主控台 (MMC) Windows 2000 系統監視器嵌入式管理單元,則您可以通過從 Performance 對象下拉式清單中選擇 .NET CLR Data 來用圖形表示 SqlClient 計數器,如 圖 1所示。請注意,您可以通過選擇 _global_ 計數器執行個體來監視所有進程,或者,您可以查看某個特定執行個體 — 每個池產生自己的一組監視器。效能監控器可列出這些計數器,並將它們作為所選定的效能物件的執行個體提供。但效能監控器不會公開這些計數器,除非有執行個體需要它們進行監視。例如,圖 1 顯示了 .NET CLR Data 效能物件,但沒有列出特定執行個體。這意味著您必須至少建立一個串連,以便使 _global_ 執行個體連同每個進程的特定執行個體一起出現。這種行為對於您的代碼來說是個問題;您將無法使用 PerformanceCounter 控制項來返回其中的任何計數器,直到 ADO.NET 在開啟串連時建立這些計數器。所以說,這個規定真有點令人左右為難。當您使用此方法時,因為缺少有效計數器執行個體,所以會引發異常 — 此時要準備好捕獲異常。 您還可以通過使用 SQL Server 效能計數器 User Connections 來監視開啟的串連的數量。該計數器被列在 Performance 對象下拉式清單中的 SQL Server: General Statistics 下。我喜歡監視 User Connections 值和一些所選定的 .NET CLR Data SqlClient 計數器(我稍後將討論此內容),因為我可以獲得我需要的資訊,而不必擔心執行個體。 使用代碼來監視效能計數器。當您需要以編程方式監視串連池時,您可以編寫代碼來監視由 SqlClient 管理的效能計數器 — 這些計數器與 MMC Windows NT 效能監控器嵌入式管理單元所提供的計數器是相同的。編寫執行監視的代碼似乎是一件有些令人畏懼的事情。但我已經提供了從 SqlClient 提供者的內部工作提取這些計數器的常式的快照(作為本文提供的可下載程式之一)。 您可以編寫檢查 表 2顯示的五個計數器的代碼。通過利用這五個計數器,您可以即時監視串連池。.NET 預期您會在效能監控器中提供一個類別 — 複製的 Performance Object — 並從那些註冊到系統的計數器中選擇適當的計數器。要訪問 SqlClient 計數器,請將該類別設定為 .NET CLR Data。 使用 PerformanceCounter 控制項。您可能會發現,在設計時向您的應用程式表單添加 PerformanceCounter 要比手工編寫代碼來訪問效能計數器更加容易。要使用 PerformanceCounter 控制項,請從“Visual Studio .NET 工具箱組件”菜單中選擇一個 PerformanceCounter,將它拖到您的應用程式表單,然後設定屬性,如 圖 2 所示。這些控制項工作在 Web Form和 WinForms 應用程式中。 因為 PerformanceCounter 控制項提供了方便的下拉式清單,所以,您可以在設計時看到任何一種效能計數器類別、計數器名稱和特定執行個體 — 您將要啟動並執行執行個體除外。這意味著您必須使用圖 2 顯示的方法來捕獲應用程式正在使用的池的適當執行個體。為了迴避這個問題,我選擇 _global_ 執行個體。再次說明一下,此方法假設某個應用程式已經至少建立了一個池,因此您需要做好不存在計數器執行個體時 ADO.NET 引發異常的準備,就像它在不存在池串連時也會引發異常一樣。 注意不準確的池計數。因為 SqlClient .NET 資料提供者中存在 .NET 架構 1.1 尚未解決的錯誤,所以,效能計數器會在池實際上已經刪除時錯誤地指示池“仍然存在”。我能通過結束 MMC 效能監控器嵌入式管理單元、然後結束 Visual Studio .NET 來驗證池已經不再存在。這些步驟說明,.NET 資料提供者在建立串連池的進程結束時會正確地刪除串連池。顯然,這種不準確性降低了效能計數器在監視池方面的有效性,所以我希望 Microsoft 將來能解決這個問題。 計數器不顯示的內容 您可能會面臨的一個問題是無法從計數器或 SqlClient 屬性看到每個池的配置。每個 SqlConnection 對象的 ConnectionString 儲存著這些池設定的密鑰。因為您不能依賴於預設設定,所以很難確定池幾乎已滿或很難使用。這會成為未來版本的 ADO.NET 的另一個方便功能。 不過,假設您知道各個串連池 ConnectionString 參數的值,則利用清單 1 中的代碼,您可以很容易地設定一個計時器來檢查您建立的特定池並報告使用百分比。然後,監視應用程式會向您發出警報,以便您可以解決問題並防止溢出。 最後,請記住,ADO.NET 採用的方法與基於 COM 的 ADO 有所不同。Visual Basic .NET 完全改變了放棄對象的方式,並且不再確保 Connection 對象在停止作用時被關閉。請確保 SqlConnection 對象(或任何 Connection 對象)在停止作用之前被關閉。 串連池是一種非常強大的功能,它可以提高應用程式的效能。但如果您不是一個出色的救生員,您的串連池會成為一個危害而不是一個優點。我希望本文討論的方法有助於您有效地監視串連池並滿足使用者的需要。 |
|