摘要:本文提供了優化 ASP 應用程序和 VBScript 的技巧。
目錄
簡介
技巧 1:在 Web 服務器上緩存常用數據
技巧 2:在 application 或 session 對象中緩存常用數據
技巧 3:在 Web 服務器磁盤上緩存數據和 Html
技巧 4:避免在 Application 或 Session 對象中緩存非靈活組件
技巧 5:不要在 Application 或 Session 對象中緩存數據庫連接
技巧 6:妙用 Session 對象
技巧 7:在 COM 對象中封裝代碼
技巧 8:晚點獲取資源,早點釋放資源
技巧 9:進程外的執行將犧牲可靠性
技巧 10:顯式使用選項
技巧 11:在子例程和函數中使用局部變量
技巧 12:將常用數據復制到腳本變量
技巧 13:避免重新定義數組
技巧 14:使用響應緩沖
技巧 15:批處理內嵌腳本和 Response.Write 語句
技巧 16:在開始長時間的任務之前先使用 Response.IsClIEntConnected
技巧 17:使用 <OBJECT> 標記實例化對象
技巧 18:使用 ADO 對象和其他組件的 TypeLib 綁定
技巧 19:利用浏覽器的驗證能力
技巧 20:在循環中避免字符串串聯
技巧 21:啟用浏覽器和代理緩存
技巧 22:盡可能使用 Server.Transfer 替代 Response.Redirect
技巧 23:在目錄 URL 尾部加斜線
技巧 24:避免使用服務器變量
技巧 25:升級為最新的和最好的版本
技巧 26:調整 Web 服務器
技巧 27:進行性能測試
技巧 28:讀取資源鏈接
--------------------------------------------------------------------------------
簡介
性能是一個特性。您需要預先設計性能,或是在日後重新編寫應用程序。換句話說,什麼是最大限度優化 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 服務器(在內存或磁盤)上緩存數據來改善性能。
緩存是典型的空間與時間的折衷。如果恰當地緩存數據,您將看到性能會有驚人的提高。為使緩存發揮效力,它必須保持經常重用的數據,而且重新計算這些數據的代價是昂貴的或比較昂貴的。如果緩存充滿了垃圾數據,則是對存儲器的浪費。
不經常變化的數據也是緩存的候選數據,因為您無須擔心數據與數據庫的同步問題。組合框、引用表、DHtml 碎片、可擴展標記語言 (XML) 字符串、菜單項和站點配置變量(包括數據源名稱 (DSN)、Internet 協議 (IP) 地址和 Web 路徑)都是緩存的候選數據。注意,您可以緩存數據的表示而不是數據本身。如果 ASP 頁不經常更改,而且緩存的成本也非常高(例如,整個產品目錄),請考慮預先生成 Html,而不是在每次請求時重新繪制。
數據應緩存在何處,有哪些緩存策略?數據經常緩存在 Web 服務器內存或 Web 服務器磁盤上。下面兩個技巧討論這些選項。
技巧 2:在 Application 或 Session 對象中緩存常用數據
ASP Application 和 Session 對象為在內存中緩存數據提供了方便的容器。既可以將數據賦予 Application 對象,也可將數據賦予 Session 對象,這些數據在 HTTP 調用中將保留在內存中。Session 數據按用戶存儲,而 Application 數據在所有用戶間共享。
何時將數據載入 Application 或 Session?通常,在 Application 或 Session 啟動時加載數據。要在 Application 或 Session 啟動時加載數據,請在下面兩函數中添加相應的代碼:Application_OnStart() 或 Session_OnStart()。這兩個函數應該位於 Global.asa;如果沒有,可以添加這些函數。也可以在第一次需要數據時加載數據。要進行上述操作,請在 ASP 頁中添加一些代碼(或編寫可重用的腳本函數),這些代碼檢查數據是否存在,並在數據不存在時加載數據。這是稱為遲緩計算的經典性能技術的例子 - 在您的確需要它之前,不進行計算。請看例子:
<%
Function GetEmploymentStatusList
Dim d
d = Application("EmploymentStatusList")
If d = "" Then
' FetchEmploymentStatusList 函數(不顯示)
' 從 DB 中取出數據,返回數組
d = FetchEmploymentStatusList()
Application("EmploymentStatusList") = d
End If
GetEmploymentStatusList = d
End Function
%>
可以為每一塊所需的數據編寫類似的函數。
數據應該以什麼格式存儲?任何變量類型均可存儲,因為所有腳本變量是各不相同的。例如,可以存儲字符串、整型或數組。通常,您將以這些變量類型之一存儲 ADO 記錄集的內容。若要獲取 ADO 記錄集衍生的數據,可以手工將數據復制到 VBScript 變量中,每次一個字段。使用一個 ADO 記錄集保留函數 GetRows()、GetString() 或 Save() (ADO 2.5),會更快更簡便。完整而詳細的內容已超出了本文的范圍。下面的演示函數使用了 GetRows() 來返回記錄集數據的數組:
' 取記錄集,以數組返回
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 作用域中緩存 ADO 記錄集本身。有兩個警告:
ADO 必須為標記的自由線程
必須使用斷開連接的記錄集。
如果不能保證滿足這兩個要求,請不要緩存 ADO 記錄集。在下面的非靈活組件和不要緩存連接技巧中,我們將討論在 Application 或 Session 作用域中存儲 COM 對象的危險。
如果在 Application 或 Session 作用域中存儲數據,這些數據將一直保留在那兒,直到在程序中改變它、Session 過期或 Web 應用程序重新啟動時為止。數據需要更新如何處理?若要用手工強制更新應用程序數據,可以調用只允許管理員訪問的數據更新 ASP 頁。另外,還可以通過函數,周期地自動刷新數據。下面的示例存儲帶緩存數據的時間戳,在指定時間間隔後刷新數據。
<%
' 未顯示錯誤處理...
Const UPDATE_INTERVAL = 300 ' 刷新時間間隔,以秒計
' 函數返回雇傭狀態列表
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
其他示例,請參閱具有 Application 數據的最快列表框(英文)。
請注意,在 Session 或 Application 對象中緩存大型數組並非上策。在訪問數組元素之前,腳本語言的語法要求建立整個數組的臨時副本。例如,如果在 Application 對象中緩存了將美國郵政編碼映射到本地氣象站的字符串數組,該字符串數組有 100,000 個元素,ASP 在找出一個字符串之前,必須將所有 100,000 個氣象站復制到臨時數組中。在這種情況下,建立帶自定義方法的自定義組件,來存儲氣象站 - 或使用一個字典組件,也許更好。
請不要在倒洗澡水時把孩子一同倒掉,對這種觀點的一個新的注解是:數組提供了對內存中相鄰關鍵-數據對的快速查找和存儲。索引字典比索引數組要慢。您應該根據具體情況選擇能夠提供最佳性能的數據結構。
技巧 3:在 Web 服務器磁盤上緩存數據和 Html
有時,數據過多不能在內存中進行緩存。“過多”是一種定性的判斷;它取決於打算消耗的內存量,還有緩存項的數量和這些項的檢索頻率。總之,如果有過多的數據要在內存中緩存,請考慮以文本或 XML 文件的形式,在 Web 服務器的硬盤上緩存數據。可以將在磁盤上緩存數據和在內存中緩存數據組合起來,為站點建立最優的緩存策略。
注意,在度量單個 ASP 頁的性能時,在磁盤上檢索數據不一定比從數據庫中檢索數據快。但是,緩存減輕了數據庫和網絡的負荷。在高負荷情況下,這將明顯提高總體通信量。在查詢成本很高時緩存查詢的結果,緩存便非常有效,例如多表聯合或復雜的存儲過程,或緩存大型的結果集。按照慣例,測試競爭方案。
ASP 和 COM 提供了幾種構建磁盤緩存方案的工具。ADO 記錄集的 Save() 和 Open() 函數,保存和加載磁盤上的記錄集。您可以使用這些方法重寫上面 Application 數據緩存技巧中的范例代碼,用 Save() 文件替換向 Application 對象寫入數據的代碼。
還有其他一些處理文件的組件:
Scripting.FileSystemObject 使您能夠創建、讀取和寫入文件。
MSXML 是隨 Internet Explorer 提供的 Microsoft(R) XML 解析器,它支持保存和加載 XML 文檔。
LookupTable 對象(在 MSN 上使用的范例)是從磁盤加載簡單列表的良好選擇。
最後,請考慮在磁盤上緩存數據的表示,而不是數據本身。預制的 HTML 可以作為 .htm 或 .asp 文件存儲在磁盤上;超級鏈接可以直接指向這些文件。可以使用商業工具,如 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 或 Application 對象中緩存時將導致性能瓶頸。靈活組件是標記為 ThreadingModel=Both 的組件(它聚集了自由線程匯集器 (FTM))或標記為 ThreadingModel=Neutral 的組件(Windows(R) 2000 和 COM+ 中新增的“中性”模型。)下列組件是非靈活的:
自由線程組件(除非它們聚集了 FTM)。
單元線程組件。
單線程組件。
已配置組件(Microsoft Transaction Server (MTS)/COM+ 庫和服務器包/應用程序)為非靈活組件,除非它們是“中性”線程的。單元線程組件和其他非靈活組件最適於在頁作用域工作(也就是說,它們在單個 ASP 頁上創建和銷毀)。
在 IIS 4.0 中,標記為 ThreadingModel=Both 的組件被視為靈活的。在 IIS 5.0 中,這已經不夠了。組件不僅必須標記為 Both,而且還必須聚集 FTM。靈活性文章說明了如何使得用“活動模板庫”編寫的 C++ 組件聚集 FTM。請注意,如果組件緩存接口指針,這些指針本身必須為靈活的、或者必須存儲在“COM 全局接口表 (GIT)”中。如果不能重新編譯 Both 線程組件,使它聚集 FTM,則可以將該組件標記為 ThreadingModel=Neutral。另外,如果不希望 IIS 進行靈活性檢查(這樣,希望非靈活組件能夠存儲在 Application 或 Session 作用域中),可以在 metabase 中設置 AspTrackThreadingModel 為 True。不主張更改 ASPTrackThreadingModel。
如果試圖在 Application 對象中存儲用 Server.CreateObject 創建的非靈活組件,IIS 5.0 將產生錯誤。可以通過在 Global.asa 中使用 <object runat=server scope=application ...> 解決該問題,但是不主張這樣做,因為這將導致匯集和串行化,說明如下。
如果緩存非靈活組件,會發生什麼錯誤呢?緩存在 Session 對象中的非靈活組件,將把會話“鎖定”到某個 ASP 工作器線程。ASP 維護著一個工作器線程池,它向請求提供服務。通常,新的請求由第一個可用的工作器線程來處理。如果 Session 被鎖定到某個線程,則該請求將不得不等待它所關聯的線程變為可用。打個比方:您進入一個超市,挑選了一些食品,然後在第 3 號收款台交款。從這以後,每當您在這個超市購買食品,都不得不始終在第 3 號收款台交款,即使是在其他收款台人少或沒人時。
將非靈活組件存儲在 Applicaton 作用域甚至會對性能產生更嚴重的影響。ASP 將不得不創建專用的線程來運行非靈活的、Applicaton 作用域內的組件。這將導致兩種後果:所有調用不得不被匯集到該線程,而且所有調用被串行化。匯集意味著:參數不得不存儲在內存的共享區;對該專用線程執行昂貴的上下文切換;組件的方法被執行;結果匯集到共享區域;以及經過另一個昂貴的上下文切換,使控制權返回原來的線程。串行化意味著所有方法必須一個挨一個地運行(同一時刻只能運行一個方法)。兩個不同的 ASP 工作器線程不可能同時執行共享組件上的方法。這將扼殺並行機制,尤其是在多處理器計算機上。更壞的是,所有非靈活的、Application 作用域內的組件都將共享一個線程(“Host STA”),所以串行化的影響更加嚴重。
是否感到困惑?下面我們提出幾個通用規則。如果您正在用 Visual Basic (6.0) 或更早版本編寫對象,請不要將它們緩存在 Application 或 Session 對象中。如果您不知道對象的線程模型,就不要緩存它。不要緩存非靈活對象,而應當在每頁上創建並釋放它們。對象將直接運行在 ASP 工作器線程上,這樣,將不會發生匯集或串行化。如果 COM 對象正運行在 IIS 框中,而且如果它們沒有花很長時間來初始化和取消,性能將是足夠的。注意,不要用該方法使用單線程對象。小心:VB 可以創建單線程的對象!如果您必須以該方式使用單線程的對象(如 Microsoft Excel 電子表格),則不要期望有很高的吞吐量。
當 ADO 被標記為自由線程時,則緩存 ADO 記錄集是安全的。要將 ADO 標記為自由線程,請使用 Makfre15.bat 文件,該文件通常位於如下目錄中:\\PRogram Files\Common\System\ADO。
警告: 如果您正在用 Microsoft Access 作為數據庫,則不應當將 ADO 標記為自由線程。通常,ADO 記錄集還必須是斷開連接的,如果您不能控制站點的 ADO 配置(例如,您是獨立的軟件廠商 [ISV],將 Web 應用程序賣給客戶,然後由他們來管理他們自己的配置),那麼不緩存記錄集可能會更好。
詞典組件也是靈活對象。LookupTable 從數據文件加載它的數據,並且它對組合框數據和配置信息是有用的。來自 Duwamish Books 的 PageCache 對象提供了目錄語義,和 Caprock Dictionary 的表現一樣。這些對象或它們的派生對象可以構成有效緩存策略的基礎。注意,Scripting.Dictionary 對象不是靈活的,所以不應當存儲在 Application 或 Session 作用域。
技巧 5:不要在 Application 或 Session 對象中緩存數據庫連接
緩存 ADO 連接通常是不好的策略。如果一個 Connection 對象存儲在 Application 中,並在所有頁上使用,那麼所有頁將競爭使用該連接。如果 Connection 對象存儲在 ASP Session 對象中,那麼將為每個用戶創建數據庫連接。這將連接池的好處毀於一旦,並對 Web 服務器和數據庫產生不必要的壓力。
取代緩存數據庫連接的方法是,在每個使用 ADO 的 ASP 頁上創建並取消 ADO 對象。這是個有效的方法,因為 IIS 具有內置的數據庫連接池。更准確的說,IIS 自動啟用 OLEDB 和 ODBC 連接池。這確保了創建並取消每個頁上的連接將是有效的。
由於被連接的記錄集中存儲有對數據庫連接的引用,所以,不應當在 Application 或 Session 對象中緩存被連接的記錄集。但是,可以安全地緩存斷開連接的記錄集,因為它不包含對其數據連接的引用。要斷開記錄集的連接,請執行如下兩個步驟:
Set rs = Server.CreateObject("ADODB.RecordSet")
rs.CursorLocation = adUseClIEnt ' 第 1 步
' 植入帶數據的記錄集
rs.Open strQuery, strProv
' 現在斷開記錄集同數據提供者和數據源的連接
rs.ActiveConnection = Nothing ' 第 2 步