系統緩存的概述
有時候總聽到大家說網站運行好慢,不知如何是好;有時候也總見到一些朋友寫的網站功能看起來非常好,但訪問性能卻極其差。如何應對這種情況,充分利用系統緩存則是首要之道。
系統緩存有什麼好處呢?舉個簡單的例子,你想通過網頁查詢某些數據,而這些數據並非實時變化,或者變化的時間是有期限的。例如查詢一些歷史數據。那麼每個 用戶每次查的數據都是一樣的。如果不設置緩存,ASP.NET也會根據每個用戶的請求重複查詢n次,這就增加了不必要的開銷。所以,可能的情況下儘量使用 緩存,從內存中返回數據的速度始終比去數據庫查的速度快,因而可以大大提供應用程序的性能。畢竟現在內存非常便宜,用空間換取時間效率應該是非常划算的。 尤其是對耗時比較長的、需要建立網絡鏈接的數據庫查詢操作等。
緩存功能是大型網站設計一個很重要的部分。由數據庫驅動的Web應用程序,如果需要改善其性能,最好的方法是使用緩存功能。
緩存的分類
從分佈上來看,我們可以概括爲客戶端緩存和服務器端緩存。如圖所示:
圖 緩存的分類
客戶端緩存—— 比如你瀏覽一個新的網站,第一次可能要花點時間才能載入整個頁面。而以後再瀏覽時,時間就會大大的縮短,原因就在於這個客戶端緩 存。現在的瀏覽器都比較智能,它會在客戶機器的硬盤上保留許多靜態的文件,比如各種gif,jpeg文件等等。等以後再瀏覽此網頁的時候,它會盡量使用本地緩存裏 面的文件。只有服務器端的文件更新了,或是緩存裏面的文件過期了,它纔會再次從服務器端下載這些東西。很多時候是IE替我們做了這件事情。
服務器端緩存—— 有些東西沒法或是不宜在客戶端緩存,那麼我們只好在服務器端想想辦法了。服務器端緩存從性質上看,又可以分爲兩種。
(1)靜態文件緩存
好多頁面是靜態的,很少改動,那麼這種文件最適於作靜態緩存。現在的IIS 6.0這部分內容是直接存放在Kernel的內存中,由HTTP.SYS直接管理。由於它在Kernel Space,所以它的性能非常的高。用戶的請求如果在緩存裏面,那麼HTTP.SYS直接將內容發送到network driver上去,不需要像以前那樣從IIS的User space的內存copy到Kernel中,然後再發送到TCP/IP stack上。Kernel level cache幾乎是現在高性能Web server的一個必不可少的特性。
(2)動態緩存
動態緩存是比較有難度的。因爲你在緩存的時候要時刻注意一個問題,那就是緩存的內容是不是已經過時了。因爲內容過時了可能會有很嚴重的後果。比如網上買賣 股票的網站。你給別人提供的價格是過時的,那人家非砍了你不可。緩存如何發現自己是不是過時就是一個非常複雜的問題。
在ASP.NET中,常見的動態緩存主要有以下幾種手段:
Ø 傳統緩存方式
Ø 頁面輸出緩存。
Ø 頁面局部緩存。
Ø 利用.NET提供的System.Web.Caching 緩存。
Ø 緩存依賴。
15.4.2 傳統緩存方式
比如將可重複利用的東西放到Application或是Session中去保存。
Session["Style"] = val;
Application["Count"] = 0;
===============================================================================
頁面輸出緩存
頁面輸出緩存是最爲 簡單的緩存機制,該機制將整個ASP.NET頁面內容保存在服務器內存中。當用戶請求該頁面時,系統從內存中輸出相關數據,直到緩存數據過期。在這個過程 中,緩存內容直接發送給用戶,而不必再次經過頁面處理生命週期。通常情況下,頁面輸出緩存對於那些包含不需要經常修改內容的,但需要大量處理才能編譯完成 的頁面特別有用。需要讀者注意的是,頁面輸出緩存是將頁面全部內容都保存在內存中,並用於完成客戶端請求。
在ASP.NET中頁面緩存的使用方法非常的簡單,只需要在aspx頁的頂部加這樣一句聲明即可:
<%@ OutputCache Duration="60" VaryByParam="none" %> |
Duration
緩存的時間(秒)。這是必選屬性。如果未包含該屬性,將出現分析器錯誤。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="CacheWebApp._16_4_3.WebForm1" %> <%@ OutputCache Duration="60" VaryByParam="none" %> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>頁面緩存示例</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label> </div> </form> </body>
</html> |
後臺代碼:
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { Label1.Text = DateTime.Now.ToString(); }
} |
如果不加<%@ OutputCache Duration="60" VaryByParam="none" %>,每次刷新頁面上的時間每次都是在變。而加了緩存聲明以後,每次刷新頁面的時間並不變化,60秒後才變化一次,說明數據被緩存了60秒。
VaryByParam
是指頁面根據使用 POST 或 GET 發送的名稱/值對(參數)來更新緩存的內容,多個參數用分號隔開。如果不希望根據任何參數來改變緩存內容,請將值設置爲 none。如果希望通過所有的參數值改變都更新緩存,請將屬性設置爲星號 (*)。
例如: http://localhost:1165/16-4-3/WebForm1.aspx?p=1
則可以在WebForm1.aspx頁面頭部聲明緩存:<%@ OutputCache Duration="60" VaryByParam="p" %>
以上代碼設置頁面緩存時間是60秒,並根據p參數的值來更新緩存,即p的值發生變化才更新緩存。
如果一直是WebForm1.aspx?p=1訪問該頁,則頁面會緩存當前數據,當p=2時又會執行後臺代碼更新緩存內容。
如果有多個參數時,如:http://localhost:1165/16-4-3/WebForm1.aspx?p=1&n=1
可以這樣聲明:<%@ OutputCache Duration="60" VaryByParam="p;n" %>
除此之外,@OutputCache 還有一些其他的屬性。@OutputCache指令中的屬性參數描述如下:
<%@ OutputCache Duration="#ofseconds" Location="Any | Client | Downstream | Server | None | ServerAndClient " Shared="True | False" VaryByControl="controlname" VaryByCustom="browser | customstring" VaryByHeader="headers" VaryByParam="parametername" CacheProfile="cache profile name | ''" NoStore="true | false" SqlDependency="database/table name pair | CommandNotification"
%> |
CacheProfile
用於調用Web.config配置文件中設置的緩存時間。這是可選屬性,默認值爲空字符 ("")。
例如:
在Web.config中加入配置:
<system.web> <caching> <outputCacheSettings> <outputCacheProfiles> <addname="CacheTest" duration="50" /> </outputCacheProfiles> </outputCacheSettings>
</caching>
</system.web>
|
頁面中聲明:
<%@ OutputCache CacheProfile="CacheTest" VaryByParam="none" %> |
注意: 包含在用戶控件(.ascx 文件)中的 @ OutputCache 指令不支持此屬性。在頁中指定此屬性時,屬性值必須與 outputCacheSettings 節下面的 outputCacheProfiles 元素中的一個可用項的名稱匹配。如果此名稱與配置文件項不匹配,將引發異常。 |
如果每個頁面的緩存時間相同,則不需要每個頁面設置,而是通過統一一個地方控制,這樣就可以更好的統一控制所有頁面的緩存時間。如果想改變緩存時間,只需要改一下web.config的配置信息即可,而不用每個頁面去修改。
VaryByControl
通過用戶控件文件中包含的服務器控件來改變緩存(值是控件ID,多控件用分號隔開)。
在 ASP.NET 頁和用戶控件上使用 @ OutputCache 指令時,需要該屬性或 VaryByParam 屬性。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="CacheWebApp._16_4_3.WebForm2" %> <%@ OutputCache Duration="60" VaryByParam="none" VaryByControl="DropDownList1" %>
<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>根據控件頁面緩存</title> </head> <body> <form id="form1" runat="server"> <div> <%=DateTime.Now %> <br> <asp:DropDownList ID="DropDownList1" runat="server"> <asp:ListItem>beijing</asp:ListItem> <asp:ListItem>shanghai</asp:ListItem> <asp:ListItem>guangzhou</asp:ListItem> </asp:DropDownList> <asp:Button ID="Button1" runat="server" Text="提交" /> </div> </form> </body>
</html> |
以上代碼設置緩存有效期是60秒,並且頁面不隨任何GET或POST參數改變(即使不使用VaryByParam屬性, 但是仍然需要在@ OutputControl指令中顯式聲明該屬性)。如果用戶控件中包含ID屬性爲“DropDownList1”的服務器控件(例如下拉框控件),那麼 緩存將根據該控件的變化來更新頁面數據。
頁面輸出緩存API
Response類的Cache屬性用於獲取頁面緩存策略。該方式的核心是調用 System.Web.HttpCachePolicy。該類主要包含用於設置緩存特定的HTTP標頭的方法和用於控制ASP.NET頁面輸出緩存的方 法。與.NET Framework 1.x中的HttpCachePolicy類相比,.NET Framework 2.0中的HttpCachePolicy類得到了擴充和發展。主要是增加了一些重要方法,例如,SetOmitVarStar方法等。由於 HttpCachePolicy類方法衆多,下面簡要說明幾個常用方法。
SetExpires方法
用於設置緩存過期的絕對時間。它的參數是一個DataTime類的實例,表示過期的絕對時間。
protected void Page_Load(object sender, EventArgs e) { // 通過API設置緩存 //相當於@OutputCache指令中的Duration屬性 Response.Cache.SetExpires(DateTime.Now.AddSeconds(10)); Response.Cache.SetExpires(DateTime.Parse("6:00:00PM")); } |
如上代碼,第一行代碼表示輸出緩存時間是60秒,並且頁面不隨任何GET或POST參數改變,等同於“<%@ OutputCache Duration="60" VaryByParam="none" %>”。第二行代碼設置緩存過期的絕對時間是當日下午6時整。
SetLastModified方法
用於設置頁面的Last-Modified HTTP標頭。Last-Modified HTTP標頭表示頁面上次修改時間,緩存將依靠它來進行計時。如果違反了緩存限制層次結構,此方法將失敗。該方法的參數是一個DataTime類的實例。
SetSlidingExpiration方法
該方法將緩存過期從絕對時間設置爲可調時間。其參數是一個布爾值。當參數爲true時,Cache-Control HTTP標頭將隨每個響應而更新。此過期模式與相對於當前時間將過期標頭添加到所有輸出集的IIS配置選項相同。當參數爲False時,將保留該設置,且 任何啓用可調整過期的嘗試都將靜態失敗。此方法不直接映射到HTTP標頭。它由後續模塊或輔助請求來設置源服務器緩存策略。
SetOmitVaryStar方法
ASP.NET 2.0新增的方法。用於指定在按參數進行區分時,響應是否應該包含vary:*標頭。方法參數是一個布爾值,若要指示HttpCachePolicy不對其VaryByHeaders屬性使用*值,則爲true;否則爲false。
SetCacheability方法
用於設置頁面的Cache-Control HTTP標頭。該標頭用於控制在網絡上緩存文檔的方式。該方法有兩種重載方式,所不同的是參數。一種重載方法的參數是HttpCacheability枚 舉值,包括NoCache、Private、Public、Server、ServerAndNoCache和ServerAndPrivate(有關這 些枚舉值的定義,可參考MSDN)。另一種方法的參數有兩個,一個參數是HttpCacheability枚舉值,另一個參數是字符串,表示添加到標頭的 緩存控制擴展。需要注意的是,僅當與Private或NoCache指令一起使用時,字段擴展名纔有效。如果組合不兼容的指令和擴展,則此方法將引發無效 參數異常。
===============================================================================
頁面局部緩存
有時緩存整個頁面是不現實的,因爲頁的某些部分可能在每次請求時都需要變化。在這些情況下,只能緩存頁的一部分。顧名思義,頁面部分緩存是將頁面部分內容保存在內存中以便響應用戶請求,而頁面其他部分內容則爲動態內容。頁面部分緩存的實現包括兩種方式:控件緩存和替換後緩存。
1. 控件緩存(也稱爲片段緩存)
這種方式允許將需要緩存的信息包含在一個用戶控件內,然後,將該用戶控件標記爲可緩存的,以此來緩存頁面輸出的部分內容。該選項允許緩存頁面中的特定內 容,而沒有緩存整個頁面,因此,每次都需重新創建整個頁。例如,如果要創建一個顯示大量動態內容(如股票信息)的頁,其中有些部分爲靜態內容(如每週總 結),這時可以將靜態部分放在用戶控件中,並允許緩存這些內容。
在ASP.NET中,提供了UserControl這種用戶控件的功能。一個頁面可以通過多個UserControl來組成。只需要在某個或某幾個UserControl裏設置緩存。
例如:
那麼可以在WebUserControl1.ascx的頁頭代碼中添加聲明語句:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs" Inherits="CacheWebApp._16_4_5.WebUserControl1" %> <%@ OutputCache Duration="60" VaryByParam="none" %>
<%=DateTime.Now %> |
調用該控件的頁面WebForm1.aspx代碼:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="CacheWebApp._16_4_5.WebForm1" %> <%@ Register src="WebUserControl1.ascx" tagname="WebUserControl1" tagprefix="uc1" %> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>控件緩存</title> </head> <body> <form id="form1" runat="server"> <div> 頁面的:<%=DateTime.Now %> </div> <div> 控件的:<uc1:WebUserControl1 ID="WebUserControl11" runat="server" /> </div> </form> </body>
</html> |
這時候刷新WebForm1.aspx頁面時,頁面的時間每次刷新都變化,而用戶控件中的時間數據卻是60秒才變化一次,說明對頁面的“局部”控件實現了緩存,而整個頁面不受影響。
2. 緩存後替換
與控件緩存正好相反。它對整個頁面進行緩存,但是頁中的某些片段是動態的,因此不會緩存這些片段。ASP.NET頁面中既包含靜態內容,又包含基於數據庫 數據的動態內容。靜態內容通常不會發生變化。因此,對靜態內容實現數據緩存是非常必要的。然而,那些基於數據的動態內容,則不同。數據庫中的數據可能每時 每刻都發生變化,因此,如果對動態內容也實現緩存,可能造成數據不能及時更新的問題。對此問題如果使用前文所述的控件緩存方法,顯然不切實際,而且實現起 來很繁瑣,易於發生錯誤。
如何實現緩存頁面的大部分內容,而不緩存頁面中的局部某些片段。ASP.NET 2.0提供了緩存後替換功能。實現該項功能可通過以下三種方法:
一是以聲明方式使用Substitution控件,
二是以編程方式使用Substitution控件API,
三是以隱式方式使用控件。
前兩種方法的核心是Substitution控件,本節將重點介紹該控件,第三種方法僅專注於控件內置支持的緩存後替換功能,本節僅做簡要說明。
(1) Substitution控件應用
爲提高應用程序性能,可能會緩存整個ASP.NET頁面,同時,可能需要根據每個請求來更新頁面上特定的部分。例如,可能要緩存頁面的很大一部分,需要動 態更新該頁上與時間或者用戶高度相關的信息。在這種情況下,推薦使用Substitution控件。Substitution控件能夠指定頁面輸出緩存中 需要以動態內容替換該控件的部分,即允許對整頁面進行輸出緩存,然後,使用Substitution控件指定頁中免於緩存的部分。需要緩存的區域只執行一次,然後從緩存讀取,直至該緩存項到期或被清除。動態區域,也就是Substitution控件指定的部分,在每次請求頁面時都執行。Substitution控件提供了一種緩存部分頁面的簡化解決方案。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="CacheWebApp._16_4_5.WebForm2" %> <%@ OutputCache Duration="60" VaryByParam="none" %> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>緩存後替換示例</title> </head> <body> <form id="form1" runat="server"> <div> 頁面緩存的時間:<%= DateTime.Now.ToString() %> </div> <div> 真實(替換)的時間:<asp:Substitution ID="Substitution1" runat="server" MethodName="getCurrentTime" /> </div> </form> </body>
</html> |
頁面後臺代碼:
public partial class WebForm2 : System.Web.UI.Page { public static string getCurrentTime(HttpContext context) { return DateTime.Now.ToString(); } } |
如上代碼所示,Substitution控件有一個重要屬性:MethodName。該屬性用於獲取或者設置當Substitution控件執行時爲回調而調用的方法名稱。該方法比較特殊,必須符合以下3條標準:
- 此方法必須被定義爲靜態方法;
- 此方法必須接受HttpContext類型的參數;
- 此方法必須返回String類型的值。
在運行情況下,Substitution控件將自動調用MethodName屬性所定義的方法。該方法返回的字符串即爲要在頁面中的Substitution控件的位置上顯示的內容。 如果頁面設置了緩存全部輸出,那麼在第一次請求時,該頁將運行並緩存其輸出。對於後續的請求,將通過緩存來完成,該頁上的其他代碼不會再運行。但 Substitution控件及其有關方法則在每次請求時都執行,並且自動更新該控件所表示的動態內容,這樣就實現了整體緩存,局部變化的替換效果。
如上代碼所示,在代碼頭部通過@ OutputCache指令設置頁面輸出緩存過期時間爲5秒,這意味着整個頁面數據都應用了緩存功能。因此,“頁面緩存的時間”所顯示的時間值來自於數據 緩存。這個時間值不會隨着刷新頁面而變化,僅當緩存過期時纔會發生更新。Substitution控件的MethodName屬性值爲 getCurrentTime。該控件顯示的內容來自於getCurrentTime方法的返回值。尤爲重要的是,雖然頁面設置了輸出緩存功能,但是每當 頁面刷新時,ASP.NET執行引擎仍然要重新執行Substitution控件,並將MethodName屬性值指定的方法返回值顯示在頁面上,因此, 顯示的是當前最新時間。
示例效果,如圖15-2所示:
圖15-2 緩存後替換
隨着頁面的刷新,真實時間在變,而頁面緩存的時間在指定的緩存時間內始終不變。
注意: l Substitution控件無法訪問頁上的其他控件,也就是說,無法檢查或更改其他控件的值。但是,代碼確實可以使用傳遞給它的參數來訪問當前頁上下文。 l 在緩存頁包含的用戶控件中可以包含Substitution控件。但是,在輸出緩存用戶控件中不能放置Substitution控件。 l Substitution控件不會呈現任何標記,其位置所顯示內容完全取決於所定義方法的返回字符串。 |
(2) Substitution控件API應用
上一小節介紹了以聲明方式使用Substitution控件實現緩存後替換的應用。本節說明另一種實現方法。該方法的核心是以編程方式利用Substitution控件API實現緩存後替換,相對於以聲明方式使用Substitution控件的方法具有更強靈活性。
通過爲Substitution指定回調方法,實現和聲明同樣的效果。Substitution的回調方法必須是
HttpResponseSubstitutionCallback委託定義的方法,它有兩個特徵:
l 一是返回值必須是String,
l 二是參數有且僅有一個,並且是HttpContext類型。
當需要以編程方式,爲緩存的輸出響應動態生成指定的響應區域時,可以在頁面代碼中將某個方法(即回調方法)的名稱作爲參 數(HttpResponseSubstitutionCallback)傳遞給Substitution。這樣Substitution就能夠使用回調 方法,並將回調方法的返回值作爲給定位置的替代內容顯示出來。
需要注意的是,回調方法必須是線程安全的,可以是作爲容器的頁面或者用戶控件中的靜態方法,也可以是其他任意對象上的靜態方法或實例方法。
下面演示一個以編程方式將 Substitution 控件添加到輸出緩存網頁。與(1)Substitution控件應用所示的示例完成同樣功能。不同的是實現方式。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm3.aspx.cs" Inherits="CacheWebApp._16_4_5.WebForm3" %> <%@ OutputCache Duration="60" VaryByParam="none" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>緩存後替換-Substitution控件API應用</title> </head> <body> <form id="form1" runat="server"> <div> 頁面緩存的時間:<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label> </div> <div> 真實(緩存替換)的時間: <asp:PlaceHolder ID="PlaceHolder1" runat="Server"></asp:PlaceHolder> </div> </form> </body>
</html> |
頁面後臺CS代碼:
protected void Page_Load(object sender, EventArgs e) { //創建一個Substitution Substitution Substitution1 = new Substitution(); //指定調用的回調方法名 Substitution1.MethodName = "GetCurrentDateTime"; PlaceHolder1.Controls.Add(Substitution1);
Label1.Text=DateTime.Now.ToString(); } public static string GetCurrentDateTime(HttpContext context) { return DateTime.Now.ToString(); } |
如上代碼所示,頁面使用@ OutputCache指令設置了輸出緩存功能,其配置數據緩存過期時間爲60秒。然而,頁面其他內容都被緩存,通過Substitution調用的回調方法顯示的內容是不被緩存的。
===============================================================================
應用程序數據緩存
System.Web.Caching 命名空間提供用於緩存服務器上常用數據的類。此命名空間包括 Cache 類,該類是一個字典,您可以在其中存儲任意數據對象,如哈希表和數據集。它還爲這些對象提供了失效功能,併爲您提供了添加和移除這些對象的方法。您還可以 添加依賴於其他文件或緩存項的對象,並在從 Cache 對象中移除對象時執行回調以通知應用程序。
/// <summary> /// 獲取當前應用程序指定CacheKey的Cache對象值 /// </summary> /// <param name="CacheKey">索引鍵值</param> /// <returns>返回緩存對象</returns> public static object GetCache(string CacheKey) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; return objCache[CacheKey]; } /// <summary> /// 設置當前應用程序指定CacheKey的Cache對象值 /// </summary> /// <param name="CacheKey">索引鍵值</param> /// <param name="objObject">緩存對象</param> public static void SetCache(string CacheKey, object objObject) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; objCache.Insert(CacheKey, objObject); } /// <summary> /// 設置當前應用程序指定CacheKey的Cache對象值 /// </summary> /// <param name="CacheKey">索引鍵值</param> /// <param name="objObject">緩存對象</param> /// <param name="absoluteExpiration">絕對過期時間</param> /// <param name="slidingExpiration">最後一次訪問所插入對象時與該對象過期時之間的時間間隔</param> public static void SetCache(string CacheKey, object objObject, DateTime absoluteExpiration, TimeSpan slidingExpiration) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; objCache.Insert(CacheKey, objObject, null, absoluteExpiration, slidingExpiration); } protected void Page_Load(object sender, EventArgs e) { string CacheKey = "cachetest"; object objModel = GetCache(CacheKey);//從緩存中獲取 if (objModel == null)//緩存裏沒有 { objModel = DateTime.Now;//把當前時間進行緩存 if (objModel != null) { int CacheTime = 30;//緩存時間30秒 SetCache(CacheKey, objModel, DateTime.Now.AddSeconds(CacheTime), TimeSpan.Zero);//寫入緩存 } } Label1.Text = objModel.ToString(); } |
以上幾種方法都很好的解決了數據緩存的問題,但由一個最大的問題是當數據發生變化了,而緩存裏還是過期的數據,只有等緩存過期後纔會重新獲取最新的數據, 這樣的話,很多時候用戶獲取的數據都是和實際數據不一致的過期數據。這同樣給用戶造成了比較大的麻煩,怎麼辦呢?接着往下看。
===============================================================================
文件緩存依賴
這種策略讓緩存依賴於一個指定的文件,通過改變文件的更新日期來清除緩存。
/// <summary> /// 獲取當前應用程序指定CacheKey的Cache對象值 /// </summary> /// <param name="CacheKey">索引鍵值</param> /// <returns>返回緩存對象</returns> public static object GetCache(string CacheKey) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; return objCache[CacheKey]; } /// <summary> /// 設置以緩存依賴的方式緩存數據 /// </summary> /// <param name="CacheKey">索引鍵值</param> /// <param name="objObject">緩存對象</param> /// <param name="cacheDepen">依賴對象</param> public static void SetCache(string CacheKey, object objObject, System.Web.Caching.CacheDependency dep) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; objCache.Insert( CacheKey, objObject, dep, System.Web.Caching.Cache.NoAbsoluteExpiration, //從不過期 System.Web.Caching.Cache.NoSlidingExpiration, //禁用可調過期 System.Web.Caching.CacheItemPriority.Default, null); } protected void Page_Load(object sender, EventArgs e) { string CacheKey = "cachetest"; object objModel = GetCache(CacheKey);//從緩存中獲取 if (objModel == null) //緩存裏沒有 { objModel = DateTime.Now;//把當前時間進行緩存 if (objModel != null) { //依賴 C:\\test.txt 文件的變化來更新緩存 System.Web.Caching.CacheDependency dep = new System.Web.Caching.CacheDependency("C:\\test.txt"); SetCache(CacheKey, objModel, dep);//寫入緩存 } }
Label1.Text = objModel.ToString(); } |
當我們改變test.txt的內容時,緩存會自動更新。這種方式非常適合讀取配置文件的緩存處理。如果配置文件不變化,就一直讀取緩存的信息,一旦配置發生變化,自動更新同步緩存的數據。
這種方式的缺點是,如果緩存的數據比較多,相關的依賴文件比較鬆散,對管理這些依賴文件有一定的麻煩。對於負載均衡環境下,還需要同時更新多臺Web服務器下的緩存文件,如果多個Web應用中的緩存依賴於同一個共享的文件,可能會省掉這個麻煩。
===============================================================================
數據庫緩存依賴
更多的時候,我們的服務器性能損耗還是在查詢數據庫的時候,所以對數據庫的緩存還是顯得特別重要,上面幾種方式都可以實現部分數據緩存功能。但問題是我們 的數據有時候是在變化的,這樣用戶可能在緩存期間查詢的數據就是老的數據,從而導致數據的不一致。那有沒有辦法做到,數據如果不變化,用戶就一直從緩存中 取數據,一旦數據變化,系統能自動更新緩存中的數據,從而讓用戶得到更好的用戶體驗。
答案是肯定的!.NET已經爲我們提供了這樣一種非常好的解決方法:SqlCacheDependency數據庫緩存依賴。
實現步驟:
下面就讓我們看一下如何實現數據庫緩存依賴功能:
第一步: 修改web.config,讓項目啓用SqlCacheDependency 。
將下列代碼加入web.config的<system.web>節:
<?xml version="1.0"?> <configuration> <appSettings/> <connectionStrings> <addname="strcodematic" connectionString="data source=127.0.0.1;initial catalog=codematic;user id=sa;password=" providerName="System.Data.SqlClient" /> </connectionStrings> <system.web> <caching> <sqlCacheDependencyenabled="true" pollTime="6000"> <databases> <addname="codematic" connectionStringName="strcodematic" /> </databases> </sqlCacheDependency> </caching>
<compilation debug="true"> </compilation> <authentication mode="Windows"/> </system.web> </configuration>
|
這裏的connectionStringName指定了在<connectionStrings>中添加的某一個連接字符串。name則是爲該SqlCacheDependency起的名字,這個名字將在第3步中用到。
SqlCacheDependency類會自動完成對此配置節信息的讀取以建立和數據庫之間的聯繫。
注意: 在<databases>節的<add name="codematic" connectionStringName="strcodematic" />中的name屬性值必須和第三步的Page_Load代碼中System.Web.Caching.SqlCacheDependency("codematic", "P_Product");中的第一個參數(數據庫名稱)相一致。 |
第二步:執行下述命令,爲 數據庫啓用緩存依賴。
如果要配置SqlCacheDependency,則需要以命令行的方式執行。
aspnet_regsql.exe工具位於Windows\\Microsoft.NET\\Framework\\[版本]文件夾中。
aspnet_regsql -C "data source=127.0.0.1;initial catalog=codematic;user id=sa;password=" -ed -et -t "P_Product"
參數-C後面的字符串是連接字符串(請替換成自己所需要的值),
參數-t後面的字符串是數據表的名字。
運行結果如圖15-3所示:
圖15-3 啓用數據庫緩存依賴
命令執行後,在指定的數據庫中會多出一個AspNet_SqlCacheTablesForChangeNotification表。
注意: 要使得7.0或者2000版本以上的SQLServer支持SqlCacheDependency特性,需要對數據庫服務器執行相關的配置。 有兩種方法配置SQLServer: 一 使用aspnet_regsql命令行工具, 二 使用SqlCacheDependencyAdmin類。 例如: aspnet_regsql -S "server" -E -d "database" –ed 或者 aspnet_regsql -S "server" -E -d "database" -et -t "table" 以下是該工具的命令參數說明: -? 顯示該工具的幫助功能; -S 後接的參數爲數據庫服務器的名稱或者IP地址; -U 後接的參數爲數據庫的登陸用戶名; -P 後接的參數爲數據庫的登陸密碼; -E 使用當前登錄用戶的 Windows 集成認證進行身份驗證。 -d 後接參數爲對哪一個數據庫採用SqlCacheDependency功能; -C 連接數據庫的連接字符串。如果您指定服務器(-S)和登錄(-U和-P,或 -E)信息,則此選項不是必需的,因爲連接字符串已經包含這些信息。 -t 後接參數爲對哪一個表採用SqlCacheDependency功能; -ed 允許對數據庫使用SqlCacheDependency功能; -dd 禁止對數據庫採用SqlCacheDependency功能; -et 允許對數據表採用SqlCacheDependency功能; -dt 禁止對數據表採用SqlCacheDependency功能; -lt 列出當前數據庫中有哪些表已經採用sqlcachedependency功能。 |
第三步:在代碼中使用緩存,併爲其設置SqlCacheDependency依賴:
/// <summary> /// 獲取當前應用程序指定CacheKey的Cache對象值 /// </summary> /// <param name="CacheKey">索引鍵值</param> /// <returns>返回緩存對象</returns> public static object GetCache(string CacheKey) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; return objCache[CacheKey]; } /// <summary> /// 設置以緩存依賴的方式緩存數據 /// </summary> /// <param name="CacheKey">索引鍵值</param> /// <param name="objObject">緩存對象</param> /// <param name="cacheDepen">依賴對象</param> public static void SetCache(string CacheKey, object objObject, System.Web.Caching.CacheDependency dep) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; objCache.Insert( CacheKey, objObject, dep, System.Web.Caching.Cache.NoAbsoluteExpiration,//從不過期 System.Web.Caching.Cache.NoSlidingExpiration,//禁用可調過期 System.Web.Caching.CacheItemPriority.Default, null); } protected void Page_Load(object sender, EventArgs e) { string CacheKey = "cachetest"; object objModel = GetCache(CacheKey);//從緩存中獲取 if (objModel == null)//緩存裏沒有 { objModel = GetData();//把當前時間進行緩存 if (objModel != null) { //依賴數據庫codematic中的P_Product表變化 來更新緩存 System.Web.Caching.SqlCacheDependency dep = new System.Web.Caching.SqlCacheDependency("codematic", "P_Product"); SetCache(CacheKey, objModel, dep);//寫入緩存 } }
GridView1.DataSource = (DataSet)objModel; GridView1.DataBind(); }
//查詢數據 private DataSet GetData() { string conString = "data source=127.0.0.1;initial catalog=codematic;user id=sa;password="; string strSQL = "SELECT * FROM P_Product"; SqlConnection myConnection = new SqlConnection(conString); DataSet ds = new DataSet(); myConnection.Open(); SqlDataAdapter adapter = new SqlDataAdapter(strSQL, myConnection); adapter.Fill(ds, "Product"); myConnection.Close(); return ds; } |
從以上代碼可以看出,和文件依賴基本相同,只是在存放緩存SetCache時存入的依賴對象不同罷了。這裏用的是SqlCacheDependency。
其中,創建SqlCacheDependency的構造方法:
public SqlCacheDependency (string databaseEntryName,string tableName) |
l databaseEntryName :是在Web.config 文件的 caching 節的 sqlCacheDependency 的 databases 元素中定義的數據庫的名稱。
l tableName :與 SqlCacheDependency 關聯的數據庫表的名稱。
這樣,只有當P_Product表的內容發生變化時,查詢操作纔會重新查詢數據更新緩存的內容,可以大大減少數據庫的重複查詢和提高系統的性能和運行效率。
===============================================================================
第三方分佈式緩存解決方案 Memcached和Cacheman
Memcached — 分佈式緩存系統
1.Memcached是什麼?
Memcached是高性能的,分佈式的內存對象緩存系統,用於在動態應用中減少數據庫負載,提升訪問速度。Memcached通過在內存裏維護一個統一 的巨大的hash表,它能夠用來存儲各種格式的數據,包括圖像、視頻、文件以及數據庫檢索的結果等。Memcached由Danga Interactive最初爲了加速 LiveJournal網站訪問速度而開發的,後來被很多大型的網站採用。起初作者編寫它可能是爲了提高動態網頁應用,爲了減輕數據庫檢索的壓力,來做的 這個緩存系統。它的緩存是一種分佈式的,也就是可以允許不同主機上的多個用戶同時訪問這個緩存系統,這種方法不僅解決了共享內存只能是單機的弊端, 同時也解決了數據庫檢索的壓力,最大的優點是提高了訪問獲取數據的速度!基於memcached作者對分佈式cache的理解和解決方案。 memcached完全可以用到其他地方 比如分佈式數據庫,分佈式計算等領域。Memcached將數據庫負載大幅度降低,更好的分配資源,更快速訪問。
2.Memcached工作機制
通過在內存中開闢一塊區域來維持一個大的hash表來加快頁面訪問速度,和數據庫是獨立的。但是目前主要用來緩存數據庫的數據。允許多個server通過 網絡形成一個大的hash,用戶不必關心數據存放在哪,只調用相關接口就可。存放在內存的數據通過LRU算法進行淘汰出內存。同時可以通過刪除和設置失效 時間來淘汰存放在內存的數據。
現在一些.NET開發人員開始放棄ASP.NET內置的緩存機制,轉而使用Memcached——一種分佈式的內存緩存系統。當運行在單獨的Web服務器 上,你可以很容易地清除一個已經確認被改變了的緩存。可惜,ASP.NET沒有一個很好的方法來支持多服務器。每個服務器上的緩存都對其他緩存的改變一無 所知。
ASP.NET允許通過基於文件系統和數據庫表的觸發器來作廢一個緩存。然而,這也存在問題,比如數據庫觸發器需要使用昂貴的輪詢,以及觸發器本身冗長的編程。但是,我們還是有其他的選擇的。
不像ASP.NET內置的緩存機制,Memcached是一個分佈式的緩存系統。任何Web服務器都能更新或刪除一個緩存項,並且所有其他的服務器都能在 下次訪問這些緩存項的時候自動獲取到更新的內容。這是通過把這些緩存項存儲在一個或者多個緩存服務器上來實現的。每一個緩存項都根據它的關鍵字的哈希值來 分配到一個服務器上。
表面看來,Memcached針對ASP.NET的API就像和內置的API一樣。這讓開發人員很容易地轉換到Memcached上,僅僅通過在代碼中查找和替換即可實現。
一個被推薦的解決方案是不根據緩存項的關鍵字來生成哈希鍵值。這將允許開發人員能夠讓一個給定頁面中需要的所有緩存項,儘量存放在同一個服務器上。可惜, 基於數據保存的地方而不是基於緩存項自身的關鍵字來生成哈希鍵,很容易產生錯誤,需要仔細來實現(這個算法)。
Memcached是基於Linux運行的,你可以在BSD的許可協議下使用Memcached。他也提供了針對C#的客戶端以及Perl、Python、PHP、Java和其他語言的API:http://www.danga.com/memcached/apis.bml。還有一個Win32的移植版本(http://jehiah.cz/projects/memcached-win32/),可以讓Memcached運行在非Linux的機器上。
Cacheman — .NET架構下的分佈式緩存項目
Cacheman據說是由微軟旗下的 Popfly 項目組成員 Sriram Krishnan 的作品。是他用業餘時間開發的。最新的情況是,微軟的 Popfly 網站已經“悄悄地”的做了更新,就是採用了 Krishnan 的 Cacheman,更新了緩存機制。該項緩存技術更新帶來的性能提升非常顯著,根據Popfly團隊中的 John Montgomery 的說法:加載一個已有的Mashup應用時,可以帶來2到6倍的性能提升。
這些說法也得到了 Krishnan 本人的確認。他提到這是Cacheman 的第一次的實際應用,並自豪的說 Cacheman 不費吹灰之力就拿下了 Popfly 的全部訪問量。
簡單介紹一下 Cacheman 這個項目。資料主要來源於 Krishnan的博客對Cacheman的介紹。
Cacheman是一個基於Windows平臺的快速分佈式哈希表。是由純託管代碼實現。中間擱置了有幾個月,直到最近纔開始重新上馬這個項目,極可能就是因爲Popfly項目需要的緣故纔開始着手的。
Krishnan本人對 memcached 很感興趣,於是創建了 Cacheman。Cacheman上有很多 memcached 的影子,比如與memcached相似的文本通訊協議。Cacheman的通訊協議公開,任何人可以根據自己偏愛的語言環境寫客戶端。 Krishnan 在自己家用電腦(2.4GHz Intel Core 2 帶2GB內存)上進入測試,達到了每秒16000次左右的請求,並且還是服務器與客戶端都是在同一臺服務器下完成的。
現這款產品還不太完善,作者自身也提到:在Cacheman做指定key的GET/SET/DELETE操作 時,客戶端需要弄清需要與哪一臺Cacheman服務器通訊,爲此要對該key做一個快速FNV哈希然後求餘得到應該和幾臺服務器中的哪臺服務器通訊。但 該法的缺點在於新增或刪除一個服務器節點時,緩存節點需要大規模遷移。修復該問題需要一致性的哈希算法,作者表示還沒有時間解決此事。作者提出了採用中心 架構的“主緩存服務器”的解決辦法,讓客戶端輪詢主緩存服務器來獲取應該與那個緩存服務器通訊,但他也覺的這樣做增加了複雜性,會帶來些新問題。
可以感覺到,由於 Cacheman 這個個人項目已經介入到 Popfly 這個正式產品中,可能很快就會被微軟吸納爲正式產品,因此如果有人採用這個產品做自己緩存的解決方案的話,應該不必太擔心後續的產品升級及文檔支持服務, 它的未來前途值的期待。說不定 Krishnan 會從 Popfly 項目脫身出來專職負責這個 Cacheman 項目。