Session在繁忙站點上使用時有幾個缺陷。繁忙的意思是:站點上每秒有上百的頁面被請求,或者同時有上千的訪問用戶。這個技巧對于那些要求水平擴展強的站點非常重要,也就是指這些站點:它們利用多個服務器完成數據裝載或者處理大量容錯。對于小型站點,比如內部網Intranet,Session是非常值得提倡的。
再次重申,ASP自動地為每一個首次點擊Web服務器的用戶創建一個Session,每一個Session占有大約10KB的內存,生存期默認是20分鐘。
使用Session最大的問題不是性能,而是擴展性,Session不能跨越多個Web服務器,一旦在一個服務器上創建了Session,它的數據就駐留在那里。這意味著,如果在Web上使用Session,你就得為每一個直接訪問存放Session服務器的用戶請求設計一個策略。這就是將用戶"粘"在Web服務器上,術語"sticky sessions"就來源于此。如果Web服務器遇到障礙,"Stuck"用戶就會丟失他們的Session狀態,因為Session不保留在磁盤上。
再次重申,ASP自動地為每一個首次點擊Web服務器的用戶創建一個Session,每一個Session占有大約10KB的內存,生存期默認是20分鐘。
使用Session最大的問題不是性能,而是擴展性,Session不能跨越多個Web服務器,一旦在一個服務器上創建了Session,它的數據就駐留在那里。這意味著,如果在Web上使用Session,你就得為每一個直接訪問存放Session服務器的用戶請求設計一個策略。這就是將用戶"粘"在Web服務器上,術語"sticky sessions"就來源于此。如果Web服務器遇到障礙,"Stuck"用戶就會丟失他們的Session狀態,因為Session不保留在磁盤上。
許多對任務要求嚴格的站點都要設立至少2個Web服務器,所以在設計嚴格任務的應用程序時,就需要執行"sticky sessions",或者簡單地避免使用Session,同時也可以采取其他保存用戶狀態到獨立Web服務器的管理技術。
如果不使用Session,一定要確認將它們關閉,這可以通過Internet服務管理器實現。如果決定使用Session,可以通過幾種方法來最小化它們的影響。
可以將不需要Session的內容(比如幫助畫面,訪問者區域,等等)移動到關閉Session的獨立ASP應用程序中。在基礎頁面上,可以給ASP一個指示,讓它不需要使用Session。將下面的代碼直接加入到ASP頁面的頭部:
<% @EnableSessionState=False %>
使用這個指示的一個很好的解釋是在框架結構中Session創建了一個有趣的問題。ASP確保在一個時刻只有一個來自Session的請求被執行,這就確保了如果瀏覽器為單個用戶請求多個頁面時,只有一個ASP請求在那時能夠接受Session,如此就避免了存取Session對象時的多線程問題。很不幸,在框架結構中的所有頁面將按照連續的順序顯示出來,一個接一個,而不是同時,所以用戶為了看到整個框架必須要等很長時間。規則是:如果一定的框架頁面沒有使用Session,就一定要告訴ASP直接使用@EnableSessionState=False。
除了使用Session對象,還有許多其他管理會話狀態的選擇。對于小數量的狀態(小于4KB),我們通常建議使用cookie、查詢字符串變量以及表單隱藏域。對于象購物車一樣的大數量數據,后臺數據庫是最合適的選擇。
如果要編寫很多VBScript或者JScript,為了提個性能,可以將代碼編寫成COM對象并且編譯使用。編譯代碼基本上比解釋性代碼運行快許多,編譯組件對象可通過"early binding"存取其他COM對象,這比在腳本中調用組件要有效。
這么做有許多優點:
COM對象有益于從商業規則中獨立出表達式規則
COM對象使代碼重用變為可能
許多開發者發現用VB,C++或者Visual J++編寫程序,比ASP更容易調試
COM對象也有缺點,包括初始開發時間和對不同編程技巧的需要。注意將少量ASP代碼做成COM對象組件不會有好處,反而可能導致性能的損失,從而失去了編譯代碼的優勢。怎樣組合使用ASP腳本和COM對象達到最佳性能是一個測試的問題。我們注意到微軟公司已經大規模在Windows 2000/IIS 5.0上提高了腳本與ADO的性能,由此,隨著IIS5.0版本的引進,減少了編譯代碼的性能優勢。
使用Option Explicit
要在ASP文件中使用Option Explicit定義,并且放置到ASP文件的頭部,從而強迫開發者在使用前聲明所有的變量。許多程序員都認為這在應用程序調試時非常有用,因為它避免了產生錯誤類型變量以及偶然創建新變量的可能。
也許更重要的是,聲明的變量要大大快于非聲明變量。
拷貝經常使用的數據到腳本變量中
在ASP中存取COM對象時,應該拷貝經常使用的對象數據到腳本變量中,這樣就減少了對COM對象的方法調用。這些調用要比存取腳本變量相對來說費時費力。當存取Collection和Dictionary對象時,使用這項技巧也減少了昂貴的查找操作。
通常,如果要不止一次地存取對象數據,就應將數據放入腳本變量中,對象數據主要也就是Request變量(表單和查詢字符串變量)。比如,站點要傳遞一個叫做UserID的查詢字符串變量,假設它將在一個特殊頁面被引用12次,那么不需要調用Request("UserID")12次,只要在ASP頁面的頭部分配給UserID一個變量,然后在頁面中使用它,這樣做就節省了11次COM方法的調用。
避免再定義數組
爭取不要再定義數組。考慮到性能問題,如果機器的物理內存大小不夠,最好按最差情況或者最佳情況設置數組的初始尺寸,需要時再重新定義。
在任何可能時使用Server.Transfer,而不要用Response.Redirect
Response.Redirect告訴瀏覽器請求另一個不同的頁面,這常常用于引導用戶到登錄頁面或者出錯處理頁面。由于重定向強迫了一個新頁面請求,結果是瀏覽器必須要與Web服務器循環2次,并且Web服務器必須處理一個額外的請求。IIS5.0引進了一個新功能Server.Transfer,它執行在同一服務器上的頁面傳輸,這將避免額外的瀏覽器-Web服務器的數據循環,形成良好的系統性能,對于用戶也有較好的響應時間。
避免使用服務器變量
存取服務器變量導致Web站點建立一個特殊的請求并收集所有的服務器變量,而并不是你要求的那個變量。這類似于在文件夾中取回一個特殊的文件,要想取回一個文件,就得首先獲取所在文件夾的信息。
不要存取非法的Request對象(比如Request("Data")),對于那些不在Request.Cookies、Request.Form、Request.QueryString或者Request.ClientCertificate中的項目,隱含就指向了Request.ServerVariables變量,而這些變量要比其他集合對象慢得多。
調整Web服務器
有幾個IIS調整參數可以提高站點性能。比如,對于IIS4.0,我們經常發現提高ASP ProcessorThreadMax參數能夠產生重大的效果,特別是在那些要等待后臺資源比如數據庫或中間件產品的站點。在IIS5.0中,你可以發現調整ASP線程通道要比調整AspProcessorThreadMax效果更佳。
最佳的配置設定取決于應用程序代碼、支持的硬件設備以及客戶端的工作量。發現最佳配置的唯一方法就是測試。
性能是一個很重要的特征。你需要事先設計好性能指標,否則日后就要為此重新編寫程序。就是說:要設想好怎樣最佳化地執行ASP程序?
本文提出了一些優化ASP應用和VBScript的技巧,許多技巧和缺陷都經過了研討。這里列出的建議已經在http://www.microsoft.com 和其他站點上進行了測試,都工作得非常好。本文假設你具備ASP開發的基本知識,包括VBScript或者JScript,ASP應用程序,ASP Session,以及其他ASP內置對象(Request,Response和Server)。
通常,ASP的執行性能遠遠不僅僅依賴ASP代碼本身!在本文的尾部列出了與性能相關的資源,它們含概了ASP和非ASP的部分,包含ActiveX Data Objects(ADO),Component Object Model(COM),數據庫(Database),以及Internet信息服務器(IIS)的配置。除了這些,還有一些非常好的鏈接值得你一看。
在Web服務器上緩存經常使用的數據
典型的情況是:ASP頁面從后臺存儲中取回數據,然后以超文本標記語言(HTML)的形式形成結果。不管數據庫的速度如何,從內存中取回數據要比從后臺存儲設備中快得多。從本地硬盤讀取數據通常也非常快。所以,提高性能可以通過緩存服務器上的數據來實現,無論是將數據緩存在內存中,或者本地硬盤中。
緩存是經典的"空間換時間"的折中方式。如果緩存得恰當,就可以看到顯著的性能提升。為了讓緩存有效,必須保證緩存數據是經常要重用的,而且也是計算起來繁瑣的。裝滿陳舊數據的緩存是對內存的浪費。
不經常改變的數據是緩存的較好對象,因為不需要隨時考慮這些數據更新后的同步操作。組合框、參考表格、DHTML代碼、擴展標記語言串、菜單以及站點配置變量(包括數據源名字DSNS,Internet協議地址IP以及Web路徑)都是很好的緩存對象。注意:要緩存數據表達式而不是數據本身。如果一個ASP頁面經常變化并且很費力去緩存(比如整個產品目錄),就要考慮預產生HTML,而不是每次發生請求時再描述它。
在Application或Session對象中緩存經常使用的數據
ASP中的Application和Session對象是在內存中緩存數據的便利容器。你可以將數據賦值給Application和Session對象,這些數據在HTTP調用期間將一直保持在內存中。Session中的數據是為每一個用戶服務的,Application中的數據是所有用戶共享的。
何時需要在Application和Session中裝入數據?通常,當應用程序啟動或者會話開始時,數據就被裝入了。為了在這時裝入數據,在Application OnStart()或者Session OnStart()中分別添加適當的代碼。這些函數位于文件Global.asa中,如果原來不存在,就添加上。也可以在數據首次需要的時候調入,在ASP頁面中添加代碼,檢查數據是否存在,如果沒有發現,就調入它。這里有一個例子,它代表了被稱為"lazy evalution"的經典性能處理技術:直到需要時,再去計算。
例子如下:
對于不同的數據,可以編寫類似的函數代碼。
數據應該按什么格式保存?任何變量類型都可以,因為所有的腳本變量都是不同的。比如說,可以保存為字符串、整型或者數據。通常,將ADO記錄集的內容存儲到這些變量類型中一個。為了從ADO記錄集中取出數據,需要手工地拷貝數據到VBScript變量中,每次一個字段。使用任意一個ADO記錄集的函數functions GetRows(),GetString() 或者 Save() (ADO 2.5)都非常得快速而且簡單,這里有個函數,描述了如何使用GetRows()返回記錄集數據的數組:
上述代碼的一個更深的技巧是為列表緩存了HTML。下面是個簡單的例子:
在合適的環境下,可以在Application或者Session中緩存ADO記錄集本身,但是有2點提示:
ADO必須是自由線程標記的
需要使用disconnected recordset方式
如果不能保證上述2個條件,就不要緩存ADO記錄集,因為這會產生很大的危險性。
再次重申,ASP自動地為每一個首次點擊Web服務器的用戶創建一個Session,每一個Session占有大約10KB的內存,生存期默認是20分鐘。
使用Session最大的問題不是性能,而是擴展性,Session不能跨越多個Web服務器,一旦在一個服務器上創建了Session,它的數據就駐留在那里。這意味著,如果在Web上使用Session,你就得為每一個直接訪問存放Session服務器的用戶請求設計一個策略。這就是將用戶"粘"在Web服務器上,術語"sticky sessions"就來源于此。如果Web服務器遇到障礙,"Stuck"用戶就會丟失他們的Session狀態,因為Session不保留在磁盤上。
再次重申,ASP自動地為每一個首次點擊Web服務器的用戶創建一個Session,每一個Session占有大約10KB的內存,生存期默認是20分鐘。
使用Session最大的問題不是性能,而是擴展性,Session不能跨越多個Web服務器,一旦在一個服務器上創建了Session,它的數據就駐留在那里。這意味著,如果在Web上使用Session,你就得為每一個直接訪問存放Session服務器的用戶請求設計一個策略。這就是將用戶"粘"在Web服務器上,術語"sticky sessions"就來源于此。如果Web服務器遇到障礙,"Stuck"用戶就會丟失他們的Session狀態,因為Session不保留在磁盤上。
許多對任務要求嚴格的站點都要設立至少2個Web服務器,所以在設計嚴格任務的應用程序時,就需要執行"sticky sessions",或者簡單地避免使用Session,同時也可以采取其他保存用戶狀態到獨立Web服務器的管理技術。
如果不使用Session,一定要確認將它們關閉,這可以通過Internet服務管理器實現。如果決定使用Session,可以通過幾種方法來最小化它們的影響。
可以將不需要Session的內容(比如幫助畫面,訪問者區域,等等)移動到關閉Session的獨立ASP應用程序中。在基礎頁面上,可以給ASP一個指示,讓它不需要使用Session。將下面的代碼直接加入到ASP頁面的頭部:
<% @EnableSessionState=False %>
使用這個指示的一個很好的解釋是在框架結構中Session創建了一個有趣的問題。ASP確保在一個時刻只有一個來自Session的請求被執行,這就確保了如果瀏覽器為單個用戶請求多個頁面時,只有一個ASP請求在那時能夠接受Session,如此就避免了存取Session對象時的多線程問題。很不幸,在框架結構中的所有頁面將按照連續的順序顯示出來,一個接一個,而不是同時,所以用戶為了看到整個框架必須要等很長時間。規則是:如果一定的框架頁面沒有使用Session,就一定要告訴ASP直接使用@EnableSessionState=False。
除了使用Session對象,還有許多其他管理會話狀態的選擇。對于小數量的狀態(小于4KB),我們通常建議使用cookie、查詢字符串變量以及表單隱藏域。對于象購物車一樣的大數量數據,后臺數據庫是最合適的選擇。
如果要編寫很多VBScript或者JScript,為了提個性能,可以將代碼編寫成COM對象并且編譯使用。編譯代碼基本上比解釋性代碼運行快許多,編譯組件對象可通過"early binding"存取其他COM對象,這比在腳本中調用組件要有效。
這么做有許多優點:
COM對象有益于從商業規則中獨立出表達式規則
COM對象使代碼重用變為可能
許多開發者發現用VB,C++或者Visual J++編寫程序,比ASP更容易調試
COM對象也有缺點,包括初始開發時間和對不同編程技巧的需要。注意將少量ASP代碼做成COM對象組件不會有好處,反而可能導致性能的損失,從而失去了編譯代碼的優勢。怎樣組合使用ASP腳本和COM對象達到最佳性能是一個測試的問題。我們注意到微軟公司已經大規模在Windows 2000/IIS 5.0上提高了腳本與ADO的性能,由此,隨著IIS5.0版本的引進,減少了編譯代碼的性能優勢。
使用Option Explicit
要在ASP文件中使用Option Explicit定義,并且放置到ASP文件的頭部,從而強迫開發者在使用前聲明所有的變量。許多程序員都認為這在應用程序調試時非常有用,因為它避免了產生錯誤類型變量以及偶然創建新變量的可能。
也許更重要的是,聲明的變量要大大快于非聲明變量。
拷貝經常使用的數據到腳本變量中
在ASP中存取COM對象時,應該拷貝經常使用的對象數據到腳本變量中,這樣就減少了對COM對象的方法調用。這些調用要比存取腳本變量相對來說費時費力。當存取Collection和Dictionary對象時,使用這項技巧也減少了昂貴的查找操作。
通常,如果要不止一次地存取對象數據,就應將數據放入腳本變量中,對象數據主要也就是Request變量(表單和查詢字符串變量)。比如,站點要傳遞一個叫做UserID的查詢字符串變量,假設它將在一個特殊頁面被引用12次,那么不需要調用Request("UserID")12次,只要在ASP頁面的頭部分配給UserID一個變量,然后在頁面中使用它,這樣做就節省了11次COM方法的調用。
避免再定義數組
爭取不要再定義數組。考慮到性能問題,如果機器的物理內存大小不夠,最好按最差情況或者最佳情況設置數組的初始尺寸,需要時再重新定義。
在任何可能時使用Server.Transfer,而不要用Response.Redirect
Response.Redirect告訴瀏覽器請求另一個不同的頁面,這常常用于引導用戶到登錄頁面或者出錯處理頁面。由于重定向強迫了一個新頁面請求,結果是瀏覽器必須要與Web服務器循環2次,并且Web服務器必須處理一個額外的請求。IIS5.0引進了一個新功能Server.Transfer,它執行在同一服務器上的頁面傳輸,這將避免額外的瀏覽器-Web服務器的數據循環,形成良好的系統性能,對于用戶也有較好的響應時間。
避免使用服務器變量
存取服務器變量導致Web站點建立一個特殊的請求并收集所有的服務器變量,而并不是你要求的那個變量。這類似于在文件夾中取回一個特殊的文件,要想取回一個文件,就得首先獲取所在文件夾的信息。
不要存取非法的Request對象(比如Request("Data")),對于那些不在Request.Cookies、Request.Form、Request.QueryString或者Request.ClientCertificate中的項目,隱含就指向了Request.ServerVariables變量,而這些變量要比其他集合對象慢得多。
調整Web服務器
有幾個IIS調整參數可以提高站點性能。比如,對于IIS4.0,我們經常發現提高ASP ProcessorThreadMax參數能夠產生重大的效果,特別是在那些要等待后臺資源比如數據庫或中間件產品的站點。在IIS5.0中,你可以發現調整ASP線程通道要比調整AspProcessorThreadMax效果更佳。
最佳的配置設定取決于應用程序代碼、支持的硬件設備以及客戶端的工作量。發現最佳配置的唯一方法就是測試。
性能是一個很重要的特征。你需要事先設計好性能指標,否則日后就要為此重新編寫程序。就是說:要設想好怎樣最佳化地執行ASP程序?
本文提出了一些優化ASP應用和VBScript的技巧,許多技巧和缺陷都經過了研討。這里列出的建議已經在http://www.microsoft.com 和其他站點上進行了測試,都工作得非常好。本文假設你具備ASP開發的基本知識,包括VBScript或者JScript,ASP應用程序,ASP Session,以及其他ASP內置對象(Request,Response和Server)。
通常,ASP的執行性能遠遠不僅僅依賴ASP代碼本身!在本文的尾部列出了與性能相關的資源,它們含概了ASP和非ASP的部分,包含ActiveX Data Objects(ADO),Component Object Model(COM),數據庫(Database),以及Internet信息服務器(IIS)的配置。除了這些,還有一些非常好的鏈接值得你一看。
在Web服務器上緩存經常使用的數據
典型的情況是:ASP頁面從后臺存儲中取回數據,然后以超文本標記語言(HTML)的形式形成結果。不管數據庫的速度如何,從內存中取回數據要比從后臺存儲設備中快得多。從本地硬盤讀取數據通常也非常快。所以,提高性能可以通過緩存服務器上的數據來實現,無論是將數據緩存在內存中,或者本地硬盤中。
緩存是經典的"空間換時間"的折中方式。如果緩存得恰當,就可以看到顯著的性能提升。為了讓緩存有效,必須保證緩存數據是經常要重用的,而且也是計算起來繁瑣的。裝滿陳舊數據的緩存是對內存的浪費。
不經常改變的數據是緩存的較好對象,因為不需要隨時考慮這些數據更新后的同步操作。組合框、參考表格、DHTML代碼、擴展標記語言串、菜單以及站點配置變量(包括數據源名字DSNS,Internet協議地址IP以及Web路徑)都是很好的緩存對象。注意:要緩存數據表達式而不是數據本身。如果一個ASP頁面經常變化并且很費力去緩存(比如整個產品目錄),就要考慮預產生HTML,而不是每次發生請求時再描述它。
在Application或Session對象中緩存經常使用的數據
ASP中的Application和Session對象是在內存中緩存數據的便利容器。你可以將數據賦值給Application和Session對象,這些數據在HTTP調用期間將一直保持在內存中。Session中的數據是為每一個用戶服務的,Application中的數據是所有用戶共享的。
何時需要在Application和Session中裝入數據?通常,當應用程序啟動或者會話開始時,數據就被裝入了。為了在這時裝入數據,在Application OnStart()或者Session OnStart()中分別添加適當的代碼。這些函數位于文件Global.asa中,如果原來不存在,就添加上。也可以在數據首次需要的時候調入,在ASP頁面中添加代碼,檢查數據是否存在,如果沒有發現,就調入它。這里有一個例子,它代表了被稱為"lazy evalution"的經典性能處理技術:直到需要時,再去計算。
例子如下:
程序代碼
<%
Function GetEmploymentStatusList
Dim d
d = Application("EmploymentStatusList")
If d = "" Then
" FetchEmploymentStatusList function (not shown)
" fetches data from DB, returns an Array
d = FetchEmploymentStatusList()
Application("EmploymentStatusList") = d
End If
GetEmploymentStatusList = d
End Function
%>
Function GetEmploymentStatusList
Dim d
d = Application("EmploymentStatusList")
If d = "" Then
" FetchEmploymentStatusList function (not shown)
" fetches data from DB, returns an Array
d = FetchEmploymentStatusList()
Application("EmploymentStatusList") = d
End If
GetEmploymentStatusList = d
End Function
%>
對于不同的數據,可以編寫類似的函數代碼。
數據應該按什么格式保存?任何變量類型都可以,因為所有的腳本變量都是不同的。比如說,可以保存為字符串、整型或者數據。通常,將ADO記錄集的內容存儲到這些變量類型中一個。為了從ADO記錄集中取出數據,需要手工地拷貝數據到VBScript變量中,每次一個字段。使用任意一個ADO記錄集的函數functions GetRows(),GetString() 或者 Save() (ADO 2.5)都非常得快速而且簡單,這里有個函數,描述了如何使用GetRows()返回記錄集數據的數組:
程序代碼
" Get Recordset, return as an Array
Function FetchEmploymentStatusList
Dim rs
Set rs = createObject("ADODB.Recordset")
rs.Open "select StatusName, StatusID from EmployeeStatus", _
"dsn=employees;uid=sa;pwd=;"
FetchEmploymentStatusList = rs.GetRows() " Return data as an Array
rs.Close
Set rs = Nothing
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() " Return data as an Array
rs.Close
Set rs = Nothing
End Function
上述代碼的一個更深的技巧是為列表緩存了HTML。下面是個簡單的例子:
程序代碼
" Get Recordset, return as HTML Option list
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 Field Binding
Do Until rs.EOF
" Next line violates Don"t Do String Concats,
" but it"s OK because we are building a cache
s = s & " <option>" & fldName & "</option>" & vbCrLf
rs.MoveNext
Loop
s = s & "</select>" & vbCrLf
rs.Close
Set rs = Nothing " See Release Early
FetchEmploymentStatusList = s " Return data as a String
End Function
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 Field Binding
Do Until rs.EOF
" Next line violates Don"t Do String Concats,
" but it"s OK because we are building a cache
s = s & " <option>" & fldName & "</option>" & vbCrLf
rs.MoveNext
Loop
s = s & "</select>" & vbCrLf
rs.Close
Set rs = Nothing " See Release Early
FetchEmploymentStatusList = s " Return data as a String
End Function
在合適的環境下,可以在Application或者Session中緩存ADO記錄集本身,但是有2點提示:
ADO必須是自由線程標記的
需要使用disconnected recordset方式
如果不能保證上述2個條件,就不要緩存ADO記錄集,因為這會產生很大的危險性。