盡管緩存管理在Windows應用程序中已經不再是個問題,但在web環境下依然是個挑戰。因為HTTP是一個無狀態的協議並且web服務無法識別不同請求的用戶。識別不同的請求究竟是哪個特定用戶發出的,並且存儲這些信息以便它在以後請求中能被重新使用,對我們來說非常重要。ASP.NET提供了很多特性用來在客戶端和服務器端存儲這些數據,但是有時我們會對“我們什麼時候使用它們(哪個)”感到疑惑。在ASP.NET中,我們會遇到像Session,Application以及Cache這些對象,為了有效地在web應用中有效地使用它們,理解他們之間的不同對我們來說非常重要。
背景
在這篇文章中,我將談到在ASP.NET中不同的緩存管理方法。在web應用中,有時需要在服務端存儲數據以避免從數據庫檢索數據和數據格式化邏輯所需的開銷來提高性能,同時在接下來的請求中我們可以跨用戶、跨應用、跨機器地重用同樣的數據。所以,為了實現這個目的我們需要在服務端緩存數據。
緩存幫我們在3個方面實現了提高服務質量
•性能(Performance)-通過減少檢索數據和格式化操作開銷,緩存提高了應用程序的性能。
•可伸縮性(Scalability)-由於緩存減少了檢索數據和格式化操作的開銷,它降低了服務端的負載,因而提高了應用程序的可伸縮性。
•可用性(Availability)-由於應用程序從緩存中讀取數據,應用程序可以在其它系統或數據庫連接失敗時繼續運行。
不同方法
在web應用中,我們可以在服務端和客戶端緩存數據、頁面等。我們分別來看一下在服務端和客戶端緩存。
服務端緩存管理
ASP.NET Session state
Session用來緩存每個用戶的信息。這意味著這些數據是不能跨用戶共享的,它只限定了創建這個會話(Session)的用戶來使用它。ASP.NET中Session就是用來區分用戶的。
Session能用三種方式來托管:
•進程內(Inproc)-會話狀態存儲在aspnet_wp.exe進程中。當應用程序域回收時Session數據會丟失。
•狀態服務器(StateServer)-會話狀態存儲在不同的進程內,可以在不同的機器上。因為它可以存儲在不同的機器上,所以這個選項支持網站群。
•Sql數據庫(SQLServer)-會話狀態存儲在SqlServer數據庫中,這個選項也支持網站群。
對於狀態服務器和Sql數據庫來說,這兩者都需要對緩存的對象進行序列化,因為要緩存的數據是要緩存到應用程序進程之外的。這兩種方式都會影響性能因為數據檢索與存儲需要話費更多時間相對進程內緩存來說。所以要根據具體需要以確定使用哪種緩存方式。
下面示例代碼展示了如何使用Session
復制代碼 代碼如下:
string empNum = Request.QueryString["empnum"];
if (empNum != null)
{
string details = null;
if (Session["EMP_DETAILS"] == null)
{
//Get Employee Details for employee number passed
details = GetEmployeeDetails(Convert.ToInt32(empNum));
Session["EMP_DETAILS"] = details;
}
else
{
details = Session["EMP_DETAILS"];
}
//send it to the browser
Response.Write(details);
}
ASP.NET application object
ASP.NET提供了一個叫Application的對象用來存儲所有用戶都可以訪問的數據。這個對象的生命周期與應用程序的生命周期一樣,當應用程序啟動時這個對象會被重新創建。與Session對象不同,Application對象可以被所有用戶請求,因為這個對象是在應用程序域中創建和管理的,因而它也是不能在Web網站群中使用的。Application對象非常適合存儲應用程序元數據(Config file data),這種數據可以被裝載到Application對象中並且在整個應用程序周期中每個用戶請求都可以訪問其中的對象而不用重新裝載。但是如果有這樣的需求:在應用程序運行中無論什麼時候對Config文件做了修改緩存數據必需失效,這時Application方式就不能提供這樣的支持了。在這種情況下,就要考慮cache對象了,下面介紹cache對象的使用。
ASP.NET cache object
ASP.NET cache object是我最喜歡的緩存機制,這是為什麼我在這裡要多說一些的原因。ASP.NET提供了一個鍵-值對(key-value pair)對象--cache對象,它可以在system.web.caching名稱空間中得到。它的范圍是應用程序域,生命周期和應用程序生命周期一致。與Session對象不同,它是可以被不同用戶來訪問的。
盡管Application和Cache對象非常相似,主要區別在於Cache對象有擁有更多的特性,像過期策略、緩存依賴。它意味著數據存儲在緩存對象可以根據預定義時間或它依賴的實體變化時過期或清楚,而這個特性Application對象是不支持的。
讓我們來討論下它支持的過期策略和緩存的依賴吧。
依賴
依賴意味著緩存的對象會被清除當依賴的實體發生變化時。所以可以定義一個依賴關系當依賴的對象發生變化時清除對應緩存對象。ASP.NET支持了兩種依賴對象。
•文件依賴(File Dependency)-它提供了這樣一種機制,當磁盤文件無論何時發生變化時自動清除緩存對象。舉例來說,我的應用程序使用XML存儲錯誤信息(錯誤號和錯誤消息的映射),用錯誤號來檢索錯誤消息。每次當我想讀取錯誤消息的時候,我不是每次都從磁盤去讀取,而是當應用啟動的時候將其放到Cache緩存裡以便以後檢索的時候再用。在程序運行過程中,當我添加新的錯誤信息或者修改已有的錯誤信息時,會發生什麼情況呢?我需要停止程序運行去修改這些信息嗎?根本不用,當做這樣修改的時候,Cache緩存中的數據會自動失效,這就是文件緩存依賴。
下面例子顯示了如何使用文件緩存來使Cache緩存失效的。所以,無論任何時候對error.xml文件作出修改時,緩存條目都會自動失效。
復制代碼 代碼如下:
object errorData;
//Load errorData from errors.xml
CacheDependency fileDependency =
new CacheDependency(Server.MapPath("errors.xml"));
Cache.Insert("ERROR_INFO", errorData, fileDependency);
•鍵依賴(Key Dependency)-鍵依賴和文件依賴非常相似,唯一的區別在於它不是依賴文件而是依賴其它條目,當Cache依賴的條目發生改變時或被刪除時,緩存會自動失效。這種方法對相互依賴的對象增加到緩存中,而且當主對象發生變化時這些相互依賴的緩存對象都要被釋放的情況下很有用。例如,員工號、姓名、薪水同時增加到了緩存當中,如果員工號發生了改變或被刪除,所有緩存中的員工信息都會被清除。在這個例子中,員工號在員工信息中充當依賴項。
下面例子顯示了如何使用鍵依賴來使緩存失效的。
復制代碼 代碼如下:
string[] relatedKeys = new string[1];
relatedKeys[0] = "EMP_NUM";
CacheDependency keyDependency = new CacheDependency(null, relatedKeys);
Cache["EMP_NUM"] = 5435;
Cache.Insert("EMP_NAME", "Shubhabrata", keyDependency);
Cache.Insert("EMP_ADDR", "Bhubaneswar", keyDependency);
Cache.Insert("EMP_SAL", "5555USD", keyDependency);
過期策略(Expiration Policy)
過期策略定義了如何以及何時讓緩存的對象過期的。
•基於時間的過期(Time based expiration)-基於時間的過期提供了讓用戶為緩存對象預定義過期的時間。這個預定義時間可以是一個絕對時間如到2005年10月31號12點,或者相對時間,相對於緩存對象的存入時間。
復制代碼 代碼如下:
//Absolute Expiration
Cache.Insert("EMP_NAME", "Shubhabrata", null,
DateTime.Now.AddDays(1), Cache.NoSlidingExpiration);
//Sliding Expiration
Cache.Insert("EMP_NAME", "Shubhabrata", null,
Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(60));
怎樣知道一個緩存對象被清除了?
上面的例子描述了如何清除緩存對象,但有時我們需要知道什麼時候對象從緩存中清除。可以,我們通過使用回調來實現。在上面錯誤信息的例子中,無論任何時候error.xml發生變化時,緩存的對象就會被清除。假設我們想要更新緩存與最新的錯誤消息。何時從緩存中清除對象,我們可以使用回調(Callback)來做進一步處理(重新加載對象到緩存中)。
下面例子顯示了如何在緩存過期時使用回調的場景。
復制代碼 代碼如下:
private void AddItemsToCache()
{
int empNum = 5435;
CacheItemRemovedCallback onEmpDetailsRemove =
new CacheItemRemovedCallback(EmpDetailsRemoved);
Cache.Insert("EMP_NUM", empNum, null,
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
onEmpDetailsRemove);
}
private void EmpDetailsRemoved(string key, object val,
CacheItemRemovedReason reason)
{
//When the item is expired
if (reason == CacheItemRemovedReason.Expired)
{
//Again add it to the Cache
AddItemsToCache();<BR> }
}
在上面的例子中,你必須注意CacheItemPriority這個參數,它和Callback參數一起使用。CacheItemPriority用來設置增加到緩存中的對象的優先級。這個優先權告訴Cache當內存一旦很低時,這個優先級會指示對象的釋放順序。這個過程被稱為清除(scavenging)。
.NET Remoting
你也許會想.NET remoting如何用於數據緩存?當我第一次聽到這個問題時,這個問題就進到了我的腦海中。正如你所知道的.NET Remoting通過單例把對象共享給各個客戶端,所以使用單例的對象可以用來緩存數據以共享數據給各個不同的客戶端。因為.NET Remoting可以運行在進程和機器之外,當我們想要緩存對象並且跨服務、跨用戶、尤其是用在網站群時,這個特性非常有用。這種方法我們可以將數據緩存到單例對象的數據成員裡並且提供方法去讀取和存儲數據。當我們實現這種方法時,我們必須確保緩存的remoting對象不被垃圾回收器清除了。因而我們必須設置Remoting對象的緩存永不過期以至永遠不會超時。我們可以重寫InitializeLifetimeService和MarshalByRefObject方法使它們返回Null。但是這樣做的主要問題是性能,通過分析使用這種方法比其它方法的性能都差。不管怎樣,應該由設計師或開發者根據具體需求選擇出最合適的方法。
內存映射文件(Memory-Mapping files)
大家都知道內存映射文件是什麼,它基於映射到物理磁盤上的文件到應用程序存儲空間的一個特定的地址范圍。這種方式允許不同的進程使用相同的數據從而增加應用程序的性能。因為使用內存映射文件在ASP.NET應用中並不流行,我個人也不建議使用這種方法因為它增加了程序的復雜性,並且.NET Framework也不支持這樣。但是如果有人喜歡使用這種方法的話,他必須為他們的需求開發出自定義的解決方案。
靜態變量(Static variables)
我們可以使用靜態變量來存儲全局的數據或對象,以便在整個應用程序生命周期來訪問它。同樣地,我們也可以使用靜態對象來緩存數據,並且可以提供方法來從緩存中檢索和存儲數據。因為靜態對象存儲在進程中,性能非常快。但是用靜態變量實現過期策略和緩存依賴是非常復雜的,我還是比較喜歡使用Cache相比用靜態變量。另一個問題是用戶自定義緩存對象必須是線程安全的,所以實現它必須特別小心。
自定義靜態緩存可以用下面方法實現:
復制代碼 代碼如下:
public class CustomCache
{
//Synchronized to implement thread-safe
static Hashtable _myCache =
Hashtable.Synchronized(new Hashtable());
public static object GetData(object key)
{
return _myCache[key];
}
public static void SetData(object key, object val)
{
_myCache[key] = val;
}
}
數據庫
我們可以使用數據庫來存儲數據來實現跨用戶、跨機器的數據共享。當我們想要緩存非常大的數據對象時,這是一種非常好的方式。使用這種方式來存儲小的數據是得不償失的(性能低),用於存儲少量數據可以尋找其它進程內的緩存機制。存儲到數據庫中的緩存數據需要經過序列化成XML來方便存儲和檢索,在.NET Framework中我們也可以使用其它類型的序列化格式。
頁面輸出緩存(ASP.NET page output caching)
有時,我們的web應用程序在一定的時間范圍內對於某些頁面來說是不會變化的,例如HR站點中,員工工資信息不會頻繁地變動,它們在一個月一般只變動一次。一般來說都是在一個月的第一天發生變化。所以,對特定的員工來說,一個月中這個員工的頁面內容是不會變化的。所以,把這些頁面在服務器上緩存起來以避免每次請求重新計算的過程,這真是個不錯的主意。為了達到這個目的,.NET為我們提供了在服務端指定特定時間緩存輸出頁面的特性;它也提供了緩存頁面片段的特性。在這兒我不再詳細去描述這種緩存方法了,因為網絡上有很多關於這方面的詳細介紹。這是一個非常長的部分如果我們現在討論它,我計劃在其它章節去討論它。
復制代碼 代碼如下:
<!-- VaryByParm - different versions of same page will be
cached based on the parameter sent through HTTP Get/Post
Location - where the page is cached -->
<%@OutputCache Duration="60" VaryByParam="empNum"
Location="Server"%>
我們來對比一下我們所討論的這些緩存:
方法 是否支持網站群? 備注
ASP.NET Session State
- InProc
- StateSerer
- SQLServer
No
Yes
Yes
客戶端緩存管理
在上面章節中我們討論了在服務端的不通緩存方式,但有時我們希望能在客戶端緩存數據和頁面以提高性能。使用客戶端緩存可以降低服務端的負載壓力,但這種緩存機制卻存在安全問題因為數據是存儲在客戶端。在客戶端緩存也有不同的方式,我將簡單地談到幾種。
Cookies
Cookie對web開發人員中是非常熟悉的概念,Cookie存儲在客戶端,當客戶端每次發送請求時都會將它發送到服務端,服務端響應時也會把它發回到客戶端。因為它限制了字節數(4096個字節),所以它只能緩存比較小的數據。它可以使用過期策略使它在一段特定的時間之後失效。下面的例子顯示了在ASP.NET中如何使用Cookie。
復制代碼 代碼如下:
if (this.Request.Cookies["MY_NAME"] == null)
{
this.Response.Cookies.Add(new HttpCookie("MY_NAME",
"Shubhabrata Mohanty"));
}
else
{
this.Response.Write(this.Request.Cookies["MY_NAME"].Value);
}
ViewState
.NET ViewState是一個新的概念。和頁面相關的數據和控件都是存儲在ViewState,這些保留值可以跨多個請求道服務器。如果你還記得,在VB-ASP應用開發中跨多個請求存儲數據是通過Hidden控件的。事實上ViewState在ASP.NET是隱藏控件的內部實現,但對比隱藏控件它做了散列化以增加安全性。去看ViewState是如何實現的,你可以打開頁面查看源代碼。ViewState也不能存儲大量數據因為它每個請求都會發送到服務端。
復制代碼 代碼如下:
protected void Page_Load(object sender, EventArgs e)
{
if (this.ViewState["MY_NAME"] == null)
{
this.ViewState["MY_NAME"] = "Shubhabrata Mohanty";
}
//txtName is a TextBox control
this.txtName.Text = this.ViewState["MY_NAME"].ToString();
}
Hidden fields
Hidden field在VB-ASP Web開發中非常流行。Hidden fields和其它控件的使用非常相似,但它在輸出頁面上是看不到的。和ViewState一樣它也不能存儲大量數據。注:隱藏框架(Hidden frames)可以在客戶端緩存數據,但不是所有浏覽器都支持隱藏框架。
復制代碼 代碼如下:
<!--In ASP.NET-->
<asp:HiddenField ID="myHiddenField" Value="Shubhabrata"
runat="server" />
<!--In HTML-->
<input id="myHiddenField" type="hidden" value="Shubhabrata" />
微軟IE浏覽器緩存
因為我們是在談論微軟的ASP.NET,為什麼不討論一下微軟的另外一種緩存能力呢?微軟的IE浏覽器提供了另一種機制在客戶端緩存頁面,這可以使用EXPIRES設置指令添加到HTML頁面或在IIS中手動設置。到IIS中的HTTP標簽屬性窗口,然後選擇使內容過期復選框。我們可以使用這個設置在客戶端緩存靜態網頁和圖片。