改進性能和樣式的 25+ asp 技巧 ------------------------------- Len Cardinal - Microsoft Consulting Services 高級顧問 George V. Reilly - Microsoft IIS Performance 主管 更新時間:2000年4月 根據 Nancy Cluts 的文章(英文)改寫 Nancy Cluts - 開發人員技術工程師 Microsoft Corporation
性能是一個特性。您需要預先設計性能,或是在日后重新編寫應用程序。換句話說,什么是最大限度優化 Active Server Pages (ASP) 應用程序性能的好策略?
本文為優化 ASP 應用程序和“Visual Basic(R) 腳本編輯器 (VBScript)”提供了許多技巧。對許多陷阱和缺陷進行了討論。本文所列的建議均在 http://www.microsoft.com 及其他站點上進行了測試,而且工作正常。本文假定您對 ASP 開發有基本的理解,包括對 VBScript 和/或 JScript、ASP Application、ASP Session 和其他 ASP 內部對象(請求、響應和服務器)。
ASP 的性能,通常不止取決于 ASP 代碼本身。我們并不想在一篇文章中囊括所有的至理名言,只在最后列出與性能相關的資源。這些鏈接包括 ASP 和非 ASP 主題,包括“ActiveX(R) 數據對象 (ADO)”、“部件對象模型 (COM)”、數據庫和“Internet 信息服務器 (IIS)”配置。這些是我們喜歡的鏈接 - 務請關注它們。
技巧 1:在 Web 服務器上緩存常用數據
典型的 ASP 頁從后端數據庫檢索數據,然后將結果轉換為超文本標記語言 (HTML)。無論數據庫的速度如何,從內存檢索數據要比從后端數據庫檢索數據快得多。從本地硬盤讀取數據通常也要比從數據庫檢索數據快得多。因此,通??梢酝ㄟ^在 Web 服務器(在內存或磁盤)上緩存數據來改善性能。
<% Function GetEmploymentStatusList Dim d d = Application("EmploymentStatusList") If d = "" Then ' FetchEmploymentStatusList 函數(不顯示) ' 從 DB 中取出數據,返回數組 d = FetchEmploymentStatusList() Application("EmploymentStatusList") = d End If GetEmploymentStatusList = d End Function %>
' 取記錄集,以數組返回 Function FetchEmploymentStatusList Dim rs Set rs = CreateObject("ADODB.Recordset") rs.Open "select StatusName, StatusID from EmployeeStatus", _ "dsn=employees;uid=sa;pwd=;" FetchEmploymentStatusList = rs.GetRows() ' 以數組返回數據 rs.Close Set rs = Nothing End Function
對上面示例的進一步改進應當是緩存該列表的 HTML,而不是緩存數組。下面是一個簡單的范例:
' 取記錄集,以“HTML 選項”列表返回 Function FetchEmploymentStatusList Dim rs, fldName, s Set rs = CreateObject("ADODB.Recordset") rs.Open "select StatusName, StatusID from EmployeeStatus", _ "dsn=employees;uid=sa;pwd=;" s = "<select name=""EmploymentStatus">" & vbCrLf Set fldName = rs.Fields("StatusName") ' ADO 字段綁定 Do Until rs.EOF ' 下面一行違背了不要進行字符串連接, ' 但這是可以的,因為我們正在建立高速緩存 s = s & " <option>" & fldName & "</option>" & vbCrLf rs.MoveNext Loop s = s & "</select>" & vbCrLf rs.Close Set rs = Nothing ' 參見盡早釋放 FetchEmploymentStatusList = s ' 以字符串返回數據 End Function
如果在 Application 或 Session 作用域中存儲數據,這些數據將一直保留在那兒,直到在程序中改變它、Session 過期或 Web 應用程序重新啟動時為止。數據需要更新如何處理?若要用手工強制更新應用程序數據,可以調用只允許管理員訪問的數據更新 ASP 頁。另外,還可以通過函數,周期地自動刷新數據。下面的示例存儲帶緩存數據的時間戳,在指定時間間隔后刷新數據。
' 函數返回雇傭狀態列表 Function GetEmploymentStatusList UpdateEmploymentStatus GetEmploymentStatusList = Application("EmploymentStatusList") End Function
' 定期更新緩存的數據 Sub UpdateEmploymentStatusList Dim d, strLastUpdate strLastUpdate = Application("LastUpdate") If (strLastUpdate = "") Or _ (UPDATE_INTERVAL DateDiff("s", strLastUpdate, Now)) Then
' FetchEmploymentStatusList 函數(不顯示) ' 從 DB 中取數據,返回一個數組 d = FetchEmploymentStatusList()
' 更新 Application 對象。用 Application.Lock() ' 來確保一致的數據 Application.Lock Application("EmploymentStatusList") = d Application("LastUpdate") = CStr(Now) Application.Unlock End If End Sub
有時,數據過多不能在內存中進行緩存。“過多”是一種定性的判斷;它取決于打算消耗的內存量,還有緩存項的數量和這些項的檢索頻率。總之,如果有過多的數據要在內存中緩存,請考慮以文本或 XML 文件的形式,在 Web 服務器的硬盤上緩存數據??梢詫⒃诖疟P上緩存數據和在內存中緩存數據組合起來,為站點建立最優的緩存策略。
注意,在度量單個 ASP 頁的性能時,在磁盤上檢索數據不一定比從數據庫中檢索數據快。但是,緩存減輕了數據庫和網絡的負荷。在高負荷情況下,這將明顯提高總體通信量。在查詢成本很高時緩存查詢的結果,緩存便非常有效,例如多表聯合或復雜的存儲過程,或緩存大型的結果集。按照慣例,測試競爭方案。
ASP 和 COM 提供了幾種構建磁盤緩存方案的工具。ADO 記錄集的 Save() 和 Open() 函數,保存和加載磁盤上的記錄集。您可以使用這些方法重寫上面 Application 數據緩存技巧中的范例代碼,用 Save() 文件替換向 Application 對象寫入數據的代碼。
還有其他一些處理文件的組件:
Scripting.FileSystemObject 使您能夠創建、讀取和寫入文件。 MSXML 是隨 Internet Explorer 提供的 Microsoft(R) XML 解析器,它支持保存和加載 XML 文檔。 LookupTable 對象(在 MSN 上使用的范例)是從磁盤加載簡單列表的良好選擇。 最后,請考慮在磁盤上緩存數據的表示,而不是數據本身。預制的 HTML 可以作為 .htm 或 .asp 文件存儲在磁盤上;超級鏈接可以直接指向這些文件??梢允褂蒙虡I工具,如 XBuilder 或 Microsoft(R) SQL Server 的 Internet 發行功能來自動化 HTML 生成過程。另外,可以將 HTML 片段 #include 到 .asp 文件。還可以使用 FileSystemObject 從磁盤讀取 HTML 文件或使用 XML 進行早期調整(英文)。
技巧 4:避免在 Application 或 Session 對象中緩存非靈活組件
雖然在 Application 或 Session 對象中緩存數據是個好主意,但是緩存 COM 對象可能有嚴重缺陷。將常用 COM 對象嵌入 Application 或 Session 對象通常具有吸引力。遺憾的是,很多 COM 對象,包括用 Visual Basic 6.0 或更早版本編寫的 COM 對象,在 Application 或 Session 對象中存儲時將導致嚴重的瓶頸。
Session 最大的問題不是性能而是可伸縮性。Session 不能跨越 Web 服務器;一旦在一個服務器上創建了 Session,它的數據就保持在那里。這意味著,如果您在 Web 領域中使用 Sessions,您將不得不為每個用戶的請求設計一種策略,以便始終將這些請求引向用戶的 Session 所在的服務器。這被稱為將用戶“粘”到 Web 服務器上。術語“粘性會話”即來源于此。由于 Session 沒有保持到磁盤上,所以,當 Web 服務器崩潰時,被“粘住”的用戶將丟失他們的 Sessions 狀態。
用于實施粘性會話的策略包括硬件和軟件解決方案。如 Windows 2000 Advanced Server 中的網絡負載平衡解決方案和 Cisco 公司的“本地指向器”解決方案可以實施粘性會話,但以犧牲一些可伸縮性為代價。這些解決方案并不完美。我們不主張您現在全盤推翻您的軟件解決方案(我們過去常用 ISAPI 篩選器和 URL 矯直對方案進行檢查)。
Application 對象也不能跨越服務器;如果您需要在 Web 領域內共享并更新 Application 數據,則需要使用后端數據庫。但只讀的 Application 數據在 Web 領域中仍然有用。
如果只是為了增加正常運行時間(用于處理故障轉移和服務器維護),大多數執行重要任務的站點將需要部署至少兩臺 Web 服務器。所以,在設計執行重要任務的應用程序時,您將需要實施“粘性會話”,或者簡單地避開 Sessions 以及其他任何在單個 Web 服務器上存儲用戶狀態的狀態管理技術。
如果當前沒有使用 Sessions,請確保將它們關閉??梢酝ㄟ^“Internet 服務管理器”(請參閱 ISM 文檔)來為應用程序執行該操作。如果決定使用 Sessions,可以采取幾個方法來將對性能的影響降低到最小。
可以將不需要 Sessions 的內容(如“幫助”屏幕、訪問者區域等)移動到關閉了 Sessions 的、單獨的 ASP 應用程序中??梢灾痦撎崾?nbsp;ASP:在給定的頁中您不需要 Session 對象;使用位于 ASP 頁頂端的如下指令:
如果您有很多 VBScript 或 JScript,那么您可以通過把代碼移動到已編譯的 COM 對象來經常改進它們的性能。已編譯的代碼通常比被解釋代碼運行得更快。已編譯的 COM 對象可以通過“早期綁定”訪問其他 COM 對象,這種調用 COM 對象方法的手段,比腳本所使用的“后期綁定”更有效。
將代碼封裝在 COM 對象種有如下好處(超越性能):
COM 對象是將表達邏輯與業務邏輯分隔開來的好辦法。 COM 對象啟用了代碼重用。 很多開發商發現,用 VB、C++ 或 Visual J++ 書寫的代碼,比 ASP 更容易調試。 COM 對象有一些缺點,包括初始開發時間以及需要不同的編程技巧。需要警告您的是,封裝“少”量的 ASP 可能會導致性能降低,而不是提高。通常,在少量 ASP 代碼封裝到 COM 對象時出現這樣的情況。這時候,創建和調用 COM 對象的開銷,超過了已編譯代碼的好處。至于 ASP 腳本和 COM 對象代碼怎樣合并才能產生最佳性能還有待測試。注意,與 Windows NT(R) 4.0/IIS 4.0 相比,Microsoft 已經在 Windows 2000/IIS 5.0 中極大地提高了腳本和 ADO 性能。這樣,已編譯代碼對 ASP 代碼的性能優勢已經隨著 IIS 5.0 的引入而降低。
有關在 ASP 中使用 COM 對象的優缺點的更多討論,請參閱 ASP 組件準則和用 COM 和 Microsoft Visual Basic 6.0 對分布式應用程序進行編程(英文)。如果您的確部署了 COM 組件,要對它們進行強度測試是非常重要的。實際上,所有 ASP 應用程序都應當作為正式過程進行強度測試。
技巧 8:晚點獲取資源,早點釋放資源
這是個小技巧。通常,最好晚點獲取資源而要早點釋放資源。這些資源包括 COM 對象、文件句柄和其他資源。
ASP 和 MTS/COM+ 都有允許您以可靠性換取性能的配置選項。當建立和部署應用程序時,應當理解這種交換。
ASP 選項
ASP 應用程序可以配置為以三種方式之一運行。在 IIS 5.0 中引入了術語“隔離級”來描述這些選項。三個隔離級值分別是低、中和高:
低級隔離。該隔離級在所有版本的 IIS 中受到支持,并且是最快的。它在主 IIS 進程 Inetinfo.exe 中執行 ASP。如果 ASP 應用程序崩潰,則 IIS 也將崩潰。(要在 IIS 4.0 下重新啟動 IIS,Web 站點管理員需要使用工具,如 InetMon,來監視站點,如果服務器失敗,將運行批處理文件來重新啟動服務器。而 IIS 5.0 則引入了可靠的重新啟動,它將自動重新啟動失敗的服務器。) 中級隔離。IIS 5.0 引入了這個新隔離級,它稱為進程外的,這是因為 ASP 運行在 IIS 進程之外。在中級隔離中,所有被配置按“中級”運行的 ASP 應用程序,將共享單個進程空間。這將減少在一個服務器上運行多個進程外的 ASP 應用程序所需的進程數。中級是 IIS 5.0 中默認的隔離級。 高級隔離。在 IIS 4.0 和 IIS 5.0 中受到支持,高級隔離也是進程外的。如果 ASP 崩潰,則 Web 服務器并不崩潰。ASP 應用程序將在下一個 ASP 請求時自動重新啟動。使用高級隔離,每個被配置為按高級運行的 ASP 應用程序,將在其自己的進程空間中運行。這樣可以保護 ASP 應用程序彼此不受干擾。它的缺點是它需要為每個 ASP 應用程序建立獨立的進程。當需要在一個服務器上主持十多個應用程序時,會增加很多開銷。 那么,哪個選項是最好的呢?在 IIS 4.0 中,運行進程外的應用程序會極大地影響性能。在 IIS 5.0 中,做了許多工作,使得進程外運行 ASP 應用程序對性能產生的影響降到了最低。實際上,在大多數測試中,在 IIS 5.0 中的 ASP 進程外應用程序,要比 IIS 4.0 中的進程內應用程序運行得更快。無論如何,進程內(低隔離級)在兩種平臺上仍然產生了最好的性能。但是,如果您的命中率相對較低或最大吞吐量較低,選擇低隔離級不會有太大的好處。所以,除非您需要每個 Web 服務器每秒處理數百或數千個頁面,否則沒有必要選擇低隔離級。同樣,應當測試多種配置并判斷哪種情形最適合您。
注意: 當您進程外運行 ASP 應用程序(中級或高級隔離)時,則在 NT4 上它們將運行在 MTS 中,而在 Windows 2000 上它們將運行在 COM+ 中。即,在 NT4 上它們運行在 Mtx.exe 中,而在 Windows 2000 上它們運行在 DllHost.exe 中。在“任務管理器”中,您可以看見這些正在運行的進程。還可以看見 IIS 如何為進程外的 ASP 應用程序配置 MTS 程序包或 COM+ 應用程序。
COM 選項
COM 組件也有三個配置選項,雖然與 ASP 選項不完全相似。COM 組件可以被:“不配置”、配置為“庫應用程序”或配置為“服務器應用程序”?!安慌渲谩笔侵覆幌?nbsp;COM+ 注冊組件。組件將運行在調用者的進程空間,就是說,它們是“進程中”的?!皫鞈贸绦颉币彩沁M程中的,但受惠于 COM+ 的服務,包括安全性、事務和環境支持?!胺掌鲬贸绦颉北慌渲脼樵谄渥约旱倪M程空間中運行。
您可能看到,不配置的組件比庫應用程序優點稍微多些。您還可能看到“庫應用程序”比“服務器應用程序”有很大的性能優點。這是因為“庫應用程序”與 ASP 運行在同一個進程中,而“服務器應用程序”則運行在自己的進程中。內部進程調用的開銷要比進程內調用的開銷大得多。而且,當在進程之間傳遞數據(如記錄集)時,必須在兩個進程之間復制所有的數據。
缺點!當使用“COM 服務器應用程序”時,如果要在 ASP 和 COM 之間傳遞對象,請確保對象實現“按值匯集”,即 MBV。實現 MBV 的對象將其自身從一個進程復制到另一個進程。這比另一種方式好,在另一種方式中,對象留在創建它的進程中,而其他進程則重復調用創建使用該對象的進程。被斷開連接的 ADO 記錄集將是按值匯集的,已連接的記錄集則不是。Scripting.Dictionary 并不實現 MBV,不會在進程之間傳遞。最后,要另外告訴 VB 程序員的是:MBV 不是通過傳遞參數ByVal 獲得的。MBV 是由原始組件創作者實現的。
怎么辦?
如果您想要以性能與可靠性的合理交換來完成您的配置,我們的推薦如下:
在 IIS 4.0 上,使用 ASP 的低隔離級別,并使用“MTS 服務器包”。 在 IIS 5.0 上,使用 ASP 的中隔離級別,并使用“COM+ 庫應用程序”。 這些是很一般的準則;通常讓公司以中或高隔離級別運行 ASP,而單一目的的 Web 服務器可運行于低隔離級別。請權衡折中并自行決定滿足需求的配置。
(注意,在上面 1,000 行表的示例中,許多瀏覽器,在看到 </table> 結束標記之前不會開始繪制表。請檢查目標瀏覽器的支持性。要解決該問題,請將表分割為具有較少行的多個表,然后在每個表后面調用 Response.Flush。新版本的 Internet Explorer 將在表完全下載之前繪制表,特別是如果指定表的列寬則繪制速度更快;這避免強制 Internet Explorer 通過度量每個單元格的內容來計算列寬。)
VBScript 語法 <% = expression %> 將“表達式”的值寫入 ASP 輸出流。如果響應緩沖沒有打開,則這些語句的每一句都會導致通過網絡,以許多小型包的形式,向瀏覽器寫入數據。這是非常慢的。另外,解釋少量腳本和 HTML,將導致在腳本引擎和 HTML 之間切換,也降低了性能。因此,請使用下面技巧:用對 Response.Write 的一個調用,替換內嵌的密集組合表達式。例如,在下面范例中,每行每字段有一個對響應流的寫入,每行都有許多 VBScript 和 HTML 之間的切換:
<table> <% For Each fld in rs.Fields %> <th><% = fld.Name %></th> <% Next While Not rs.EOF %> <tr> <% For Each fld in rs.Fields %> <td><% = fld.Value %></td> <% Next </tr> <% rs.MoveNext Wend %> </table>
下面是更有效的代碼,每行中有一個對響應流的寫入。所有代碼均包含在一個 VBScript 塊內:
<table> <% For each fld in rs.Fields Response.Write ("<th>" & fld.Name & "</th>" & vbCrLf) Next While Not rs.EOF Response.Write ("<tr>") For Each fld in rs.Fields %> Response.Write("<td>" & fld.Value & "</td>" & vbCrLf) Next Response.Write "</tr>" Wend %> </table>
流行的瀏覽器具有對以下功能的高級支持,例如 XML、DHTML、java 小程序以及遠程數據服務。請盡量利用這些功能。所有這些技術,都可以通過執行客戶端的驗證和數據緩存,減少了與 Web 服務器之間的往返。如果您正在運行智能瀏覽器,該瀏覽器可以為您進行一些驗證(例如,在運行 POST 之前檢查信用卡的校驗和否有效)。重申一次,請盡量使用這些功能。由于削減了客戶端到服務器的往返路程,將減少對 Web 服務器的壓力,并且削減了網絡通信量(雖然發送給瀏覽器的初始頁面可能更大),服務器訪問的所有后端資源也削減了。而且用戶不必經常提取新頁,使用戶的感受好一些。這并不減輕對服務器端驗證的需要。還是應該經常進行服務器端的驗證。這樣能夠防止由于某些原因從客戶端來的壞數據,例如黑客,或者不運行客戶端驗證程序的瀏覽器。
許多站點由獨立于瀏覽器創建的 HTML 組成。這一點經常阻礙開發人員利用可以提高性能的流行瀏覽器功能。對于真正高性能的、必須關心瀏覽器的站點,良好的策略是針對流行的瀏覽器優化您的頁面。在 ASP 中使用“瀏覽器性能組件”,很容易檢測到瀏覽器的功能。諸如 Microsoft FrontPage 等工具,能幫助您設計使用所希望的目標瀏覽器和 HTML 版本的代碼。更詳細的討論,請查看 When is Better Worse? Weighing the Technology Trade-Offs(英文)。
技巧 20:在循環中避免字符串串聯
許多人在循環中創建類似這樣的字符串:
s = "<table>" & vbCrLf For Each fld in rs.Fields s = s & " <th>" & fld.Name & "</th> " Next
While Not rs.EOF s = s & vbCrLf & " <tr>" For Each fld in rs.Fields s = s & " <td>" & fld.Value & "</td> " Next s = s & " </tr>" rs.MoveNext Wend
s = s & vbCrLf & "</table>" & vbCrLf Response.Write s
特別是在將 ADO 記錄集轉換到 HTML 表時,請考慮使用 GetRows 或 GetString。
如果用 JScript 連接字符串,強烈建議使用 += 操作符;即用 s += "某字符串", 而不是 s = s + "某字符串"。
技巧 21:啟用瀏覽器和代理緩存
默認情況下,ASP 禁用瀏覽器和代理中的緩存。這將很有意義,因為 ASP 生來就是動態的,具有潛在地對時間敏感的信息。如果有一個不需要對每次查看進行刷新的頁,則應該啟用瀏覽器和代理緩存。這使得瀏覽器和代理能在某一段時間內,使用某一頁的緩存副本,這時間的長短可以控制。緩存能明顯減輕服務器負荷,使用戶的感受好一些。
Response.Redirect 通知瀏覽器,請求一個不同的頁面。該函數經常用于重定向用戶到登錄或錯誤頁面。既然重定向強制一個新頁請求,瀏覽器就必須做兩次到 Web 服務器的往返,而且 Web 服務器必須處理額外的請求。IIS 5.0 引入一個新的函數,Server.Transfer,該函數執行傳送到相同服務器上的不同 ASP 頁。這樣避免了額外的、從瀏覽器到 Web 服務器的往返,從而改善了整體系統性能,同時改善了對用戶的響應時間。請查看重定向中的新方向(英文),它討論了 Server.Transfer 和 Server.Execute。
也可以查看Leveraging ASP in IIS 5.0中有關 IIS 5.0 和 ASP 3.0 新功能的完全列表。(英文)
如上所述,性能是一種指標。如果您正努力改進站點的性能,請先設置性能目標,然后提高性能直到達到目標為止。請不要將所有的性能測試放在項目的最后。往往到了項目的最后,再做非做不可的體系結構改動已為時太晚,并使客戶失望。性能測試是日常測試的一部分。性能測試可以針對獨立組件進行,例如 ASP 頁面或 COM 對象,也可以將站點作為一個整體進行。
許多人使用單一的瀏覽器請求頁面來測試他們 Web 站點的性能。這將使您對站點的響應有很好的感覺,但對于站點在有負荷下的性能卻一無所知。
通常,要準確地測量性能,需要專用的測試環境。這個環境應該由那些,在處理器速度、處理器個數、內存、硬盤、網絡配置等方面,能模擬產品硬件的硬件組成。然后,需要定義客戶端的工作負荷:有多少并發用戶;他們提出請求的頻率;他們將訪問的頁面類型等等。如果您無法從站點獲得實際的使用數據,則需要估計它們。最后,需要一個能模擬預期客戶端工作負荷的工具。在這些工具的幫助下,可以開始回答一些問題,例如,如果我有 N 個并發用戶,需要多少臺服務器?您還能找出瓶頸和優化的目標。
下面列出了一些好的 Web 強度測試工具。極力推薦“Microsoft Web 應用程序強度測試 (WAS)”工具包。WAS 允許記錄測試腳本,然后模擬成百或上千個訪問 Web 服務器的用戶。WAS 報告大量統計結果,包括每秒請求數、響應時間的分布和錯誤計數。WAS 具有增強客戶端和基于 Web 的接口;Web 接口允許進行遠程測試。