ASP.NET Cache的一些總結分享

來源:互聯網
上載者:User

1.1.1 摘要
最近我們的系統面臨著嚴峻效能瓶頸問題,這是由於訪問量增加,用戶端在同一時間請求增加,這迫使我們要從兩個方面解決這一問題,增加硬體和提高系統的效能。

大家可以通過各種各樣的方法去最佳化我們系統,本篇博文將介紹通過Cache方法來最佳化系統的效能,減輕系統的負擔。

1.1.2 本文

不同位置的緩衝

在Web應用程式中的使用緩衝位置主要有:用戶端瀏覽器緩衝、用戶端和伺服器中以及伺服器端,因此緩衝可以分為以下幾類:

用戶端緩衝(Client Caching)
代理緩衝(Proxy Caching)
反向 Proxy緩衝(Reverse Proxy Caching)
伺服器緩衝(Web Server Caching)
ASP.NET中的緩衝
ASP.NET中有兩種緩衝類型:輸出緩衝和資料緩衝。

輸出緩衝:這是最簡單的緩衝類型,它儲存發送到用戶端的頁面副本,當下一個用戶端發送相同的頁面請求時,此頁面不會重建(在緩衝有限期內),而是從緩衝中擷取該頁面;當然由於緩衝到期或被回收,這時頁面會重建。

資料緩衝

除此之外,還有兩個特殊的緩衝:片段快取和資料來源緩衝。

片段快取:這是一種特殊的輸出緩衝,它不是緩衝整個頁面,而是緩衝部分頁面;由於緩衝整個頁面通常並不可行,因為頁面的某些部分是針對使用者定製的(例如使用者登陸資訊),但我們可以把應用程式中共用的部分進行緩衝,這時我們可以考慮使用片段快取和使用者控制項緩衝。

資料來源緩衝:是建立在資料來源控制項的緩衝,它包括SqlDataSource、ObjectDataSource和XmlDataSource控制項。資料來源緩衝使用資料緩衝方式,不同的是我們不需要通過顯示方法處理緩衝;我們只需設定相應的屬性,然後資料來源控制項就能儲存和檢索資料。

輸出緩衝
輸出緩衝可以把最終呈現的頁面緩衝起來,當用戶端再次請求同一頁面時,控制對象不再重新建立,頁面的生命週期不再啟動,無需再次執行代碼,通過在緩衝中擷取緩衝的頁面。

現在我們設計一個頁面,每當使用者發送頁面請求時,就擷取當前代碼執行的時間,然後顯示在頁面上。

圖1輸出緩衝

這是再簡單不過的例子,每當使用者發送頁面請求都會更新頁面顯示的時間,這是由於每次請求都擷取了一個新的頁面,實際情況中,我們並不需要即時的響應使用者每個頁面請求,我們可以通過輸出緩衝把頁面緩衝起來每當使用者發送同一頁面請求時,而且在緩衝有效期間,可以通過輸出緩衝把緩衝的頁面返回給使用者。

我們要實現輸出緩衝,只需在頁面中添加如下代碼: 複製代碼 代碼如下:<!-- Adds OutputCache directive -->
<%@ OutputCache Duration="23" VaryByParam="None" %>

它支援五個屬性,其中兩個屬性Duration和VaryByParam是必填的

Duration

必需屬性。頁面應該被緩衝的時間,以秒為單位。必須是正整數。

Location

指定應該對輸出進行緩衝的位置。如果要指定該參數,則必須是下列選項之一:Any、Client、Downstream、None、Server 或 ServerAndClient。

VaryByParam

必需屬性。Request 中變數的名稱,這些變數名應該產生單獨的緩衝條目。"none" 表示沒有變動。"*" 可用於為每個不同的變數數組建立新的緩衝條目。變數之間用 ";" 進行分隔。

VaryByHeader

基於指定的標題中的變動改變緩衝條目。

VaryByCustom

允許在 global.asax 中指定自訂變動(例如,"Browser")。

表1輸出緩衝屬性

這裡我們把輸出緩衝的有效期間設定為23秒,也就是說,當緩衝超過有效期間就會被回收;當使用者再次請求該頁面時,就要重新建立頁面。

用戶端緩衝
另一種選擇是用戶端緩衝,如果使用者在瀏覽器中點擊[上一頁] 按鈕或在地址欄中重新輸入URL,那麼在這種情況下,瀏覽器將從緩衝擷取頁面;然而,如果使用者點擊[重新整理] 按鈕,那麼瀏覽器中緩衝將失效,瀏覽器發送頁面請求。

如果我們要使用用戶端緩衝,只需指定OutputCache中的屬性Location=”Client”就OK了,具體代碼如下所示:

複製代碼 代碼如下:<!-- Sets client OutputCache -->
<%@ OutputCache Duration="23" VaryByParam="None" Location="Client" %>

通過在OutputCache中添加Location屬性,我們實現了用戶端緩衝,通過設定用戶端緩衝我們能夠減少的用戶端請求,也許有人會問:“每個使用者第一次頁面請求都需要伺服器來完成,這不能很好的減少服務的壓力”。的確是這樣,相對於伺服器緩衝,用戶端緩衝並沒有減少代碼的執行和資料庫的操作,但是當我們把包含個人化資料的頁面緩衝在伺服器中,用戶端請求頁面時,由於不同的使用者個人化資料不同,這將會導致請求出現錯誤,所以我們可以使用片段快取把公用的部分緩衝起來或用戶端緩衝把使用者資訊緩衝起來。

Query String緩衝
在前面的例子中,我們把OutputCache中的VaryByParam屬性設定為None,那麼ASP.NET程式只緩衝一個頁面副本;如果頁面請求包含查詢參數,那麼在緩衝的有效期間內,我們只可以查看到只是緩衝結果,假設我們有個報表程式,它提供使用者根據產品名稱查詢相關的產品資訊。

首先我們建立兩個頁面:查詢和結果頁面,由於時間關係我們已經把頁面設計好了,具體如下所示:

圖2報表程式

首先我們提供查詢頁面,讓使用者根據成品名稱(ProductName)查詢相應的成品資訊,具體的代碼如下: 複製代碼 代碼如下:protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
// Gets product id from table Production.Product.
// Then binding data to drop down list control.
InitControl(GetProductId());
}
}
/// <summary>
/// Handles the Click event of the btnSubmit control.
/// Redirects to relative product information page.
/// </summary>
protected void btnSubmit_Click(object sender, EventArgs e)
{
Response.Redirect(string.Format("Product.aspx?productname={0}", ddlProductName.SelectedValue));
}

當使用者點擊Submit按鈕後,跳轉到Product頁面並且在Url中傳遞查詢參數——產品名稱(ProducName)。

接下來,我們繼續完成查詢頁面,由於在前一頁面中傳遞了查詢參數ProductName,那麼我們將根據ProductName查詢資料庫擷取相應的產品資訊,具體代碼如下所示: 複製代碼 代碼如下:protected void Page_Load(object sender, EventArgs e)
{
// Get product name.
string productName = Request.QueryString["productname"];

// Binding data to data grid view control.
InitControl(this.GetData(productName));
}

/// <summary>
/// Inits the control.
/// </summary>
/// <param name="ds">The dataset.</param>
private void InitControl(DataSet ds)
{
dgvProduct.DataSource = ds;
dgvProduct.DataBind();
}

/// <summary>
/// Gets the data.
/// </summary>
/// <param name="productName">Name of the product.</param>
/// <returns>Returns dataset</returns>
private DataSet GetData(string productName)
{
// The query sql base on product name.
string sql =
string.Format(
"SELECT Name, ProductNumber, SafetyStockLevel, ReorderPoint, StandardCost, DaysToManufacture "
+ "FROM Production.Product WHERE ProductNumber='{0}'",
productName);

// Get data from table Production.Product.
using (var con = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLCONN"].ToString()))
using (var com = new SqlCommand(sql, con))
{
com.Connection.Open();
////gdvData.DataSource = com.ExecuteReader();
////gdvData.DataBind();
var ada = new SqlDataAdapter(com);
var ds = new DataSet();
ada.Fill(ds);
return ds;
}
}

前面樣本,我們通過Request的屬性QueryString擷取ProductName的值,然後根據ProductName查詢資料庫,最後把擷取資料繫結到Datagridview控制項中(註:前面執行個體沒有考慮SQL Injection問題)。

圖3查詢結果

現在我們在頁面中添加輸出緩衝,如下代碼: 複製代碼 代碼如下:<!-- Adds OutputCache directive -->
<%@ OutputCache Duration="30" VaryByParam="None" %>

前面提到當輸出緩衝的屬性VaryByParam=”None”時,ASP.NET程式在緩衝有效期間內只緩衝一個頁面副本;現在我們在緩衝有效期間內(30s)再發送請求。

圖4查詢結果

通過我們發現,現在查詢參數ProductName=BK-M18B-40,但查詢結果依然是ProductName=BB-9108的資料,這是由於ASP.NET程式在緩衝有效期間內只緩衝一個頁面副本。

通過上面的樣本,我們發現只緩衝一個頁面是不適用包含查詢參數的網頁輸出快取;其實前面的樣本我們只需稍稍改動就能符合查詢參數的情況了,想必大家已經知道了,只需把VaryByParam屬性設定為“*”就OK了。

圖5查詢結果

現在查詢可以擷取相應的結果,如果查詢參數和前一個請求相同並且該頁面緩衝有效,那麼緩衝將被重用,否則,建立一個新的頁面緩衝。

由於ASP.NET給每個查詢參數都添加了輸出緩衝,但我們要注意的是是否真的有必要緩衝每個查詢參數都緩衝一個頁面副本,假設查詢Url中增加一個參數參數ProductId,那麼現在Url中就有兩個查詢參數了(ProductName和ProductId)。

前面我們把VaryByParam設定為“*”,所為ASP.NET程式對ProductName和ProductId都建立頁面緩衝,如果我們只針對ProductName建立頁面緩衝,這時我們可以修改VaryByParam,具體如下所示: 複製代碼 代碼如下:<!-- Sets VaryByParam property-->
<%@ OutputCache Duration="30" VaryByParam="productname" %>

自訂緩衝控制項
前面我們介紹了通過查詢參數實現緩衝一個或多個頁面,其實ASP.NET也允許我們自訂緩衝方式來決定是否快取頁面或重用現有的,這時我們可以通過設定VaryByCustom屬性來實現。

假設,現在我們要設計基於不同UserHostName的緩衝,由於程式在執行過程中,首先調用全域方法GetVaryByCustomString()來確定是否快取頁面面或重用現有的,所以我們可以通過重寫GetVaryByCustomString()方法實現基於UserHostName的緩衝,首先我們建立一個Global.asax檔案然後重新全域方法GetVaryByCustomString()具體實現如下: 複製代碼 代碼如下:/// <summary>
/// Gets vary cache based on custom string value.
/// </summary>
/// <param name="context">Http context.</param>
/// <param name="custom">custom string</param>
/// <returns></returns>
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (string.Equals(custom, "UserHostName", StringComparison.OrdinalIgnoreCase))
{
// Indicates that the cache should be vary on user host name.
return Context.Request.UserHostName;
}
return base.GetVaryByCustomString(context, custom);
}

前面我們重寫了GetVaryByCustomString()方法,使得UserHostName值不同時,擷取相應的緩衝值。

然後讓程式基於UserHostName建立緩衝,所以我們要在頁面添加以下代碼: 複製代碼 代碼如下:<!-- set vary cache based on custom string value -->
<%@ OutputCache Duration="30" VaryByParam="None" VaryByCustom="UserHostName" %>

我們通過自訂現在GetVaryByCustomString()方法,實現了Web程式根據UserHostName實施不同的緩衝方式,其實,我們還可以實現更多種類緩衝方案,例如:基於使用者角色、時間和Url等等。

片段快取
在某些情況下,我們不能緩衝整個頁面,但我們仍想緩衝部分頁面從而減輕系統的負擔;其實,我們可以通過兩種方法實現:片段快取和資料緩衝.

為了實現片段快取,我們需要建立自訂控制項緩衝部分頁面,然後我們把OutputCache指令添加到自訂控制項中,這樣整個頁面將不會被緩衝,而自訂緩衝控制項除外。

前面我們介紹了輸出緩衝的使用,只需在頁面中添加OutputCache指令,假設我們要在幾個頁面中添加輸出緩衝這可能比較簡單,但我們要在幾十個頁面中添加輸出緩衝功能,而且前面介紹的例子中Duration屬性值都是直接Hard code到每個頁面中,如果我們需要修改Duration屬性值,那麼就必須修改每個頁面了,ASP.NET還需要重新編譯這些頁面,這不利於我們的維護,最重要的是增加了我們的工作量。

其實,我們可以在web.config檔案中定義一個outputCacheProfile(ProductCacheProfile),然後在頁面中添加CacheProfile屬性並且賦值為ProductCacheProfile,web.config檔案設定如下: 複製代碼 代碼如下:<caching>
<!-- Sets out put cache profile-->
<outputCacheSettings>
<outputCacheProfiles>
<add name="ProductCacheProfile" duration="30"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>現在,我們在頁面中添加CacheProfile屬性,並且設定為ProductCacheProfile,如下所示:

複製代碼 代碼如下:<!-- set CacheProfile property -->
<%@ OutputCache CacheProfile="ProductCacheProfile" VaryByParam="None" %>

資料緩衝
Cache對象是安全執行緒:這表示無需顯式實現鎖定或解鎖,在添刪Cache對象中的元素,然而,在Cache對象中元素必須是安全執行緒的。例如,我們建立一個實體Product,而且存在多個用戶端可能同時操作該對象的情況,這時我們必須為實體Product實現鎖定和解鎖操作(同步操作請參考《單例模式(Singleton)的6種實現》)。

Cache對象中的快取項目自動移除:當緩衝到期,依賴項被修改或記憶體不足緩衝ASP.NET會自動移除該快取項目。

快取項目支援依賴關係:我們可以給快取項目添加檔案、資料庫表或其他資源類型的依賴關係。

SqlDataSource緩衝
當我們在SqlDataSource控制項中啟用緩衝,它緩衝SelectCommand中的結果;如果SQL查詢語句中帶有參數時,SqlDataSource控制項會緩衝每一個參數值對應的結果。

這跟我們之前通過輸出緩衝實現報表程式緩衝查詢頁面效果一樣,所以我們將使用SqlDataSource緩衝實現該效果。

假設我們要提供一個報表程式,讓使用者通過選擇產品名稱(ProductName),擷取相應的產品資訊。

首先,我們在頁面中建立兩個資料來源控制項:sourceProductName和sourceProduct,接著把資料來源分別綁定到Dropdownlist和Gridview中,具體實現如下: 複製代碼 代碼如下:<!-- The product number datasource START -->
<asp:SqlDataSource ID="sourceProductName" runat="server" ProviderName="System.Data.SqlClient"
EnableCaching="True" CacheDuration="3600" ConnectionString="<%$ ConnectionStrings:SQLCONN %>"
SelectCommand="SELECT ProductNumber FROM Production.Product"></asp:SqlDataSource>
<!-- The product number datasource END -->

<!-- The product datasource START -->
<asp:SqlDataSource ID="sourceProduct" runat="server" ProviderName="System.Data.SqlClient"
EnableCaching="True" CacheDuration="3600" ConnectionString="<%$ ConnectionStrings:SQLCONN %>"
SelectCommand="SELECT Name, ProductNumber, SafetyStockLevel, ReorderPoint, StandardCost, DaysToManufacture
FROM Production.Product WHERE ProductNumber=@ProductNumber">
<SelectParameters>
<asp:ControlParameter ControlID="ddlProductNumber" Name="ProductNumber" PropertyName="SelectedValue" />
</SelectParameters>
</asp:SqlDataSource>
<!-- The product number datasource END -->

<!-- Binding the product number to gridview control -->
<!-- NOTE: Due to search and result in the same page, so need to set AutoPostBack is True-->
<asp:DropDownList ID="ddlProductNumber" AutoPostBack="True" DataSourceID="sourceProductName"
DataTextField="ProductNumber" runat="server">
</asp:DropDownList>

<!-- Binding the product datasource to gridview control -->
<asp:GridView ID="gvProduct" runat="server" DataSourceID="sourceProduct" CssClass="Product">
</asp:GridView>

現在我們對報表程式進行查詢,如果ProudctName之前沒有被緩衝起來就會建立相應的緩衝,而已經緩衝起來的將被重用,查詢結果如下:


圖6查詢結果

緩衝的依賴關係
快取項目之間的依賴

ASP.NET Cache允許我們建立緩衝之間的依賴關係,即一個快取項目依賴於另一個快取項目;以下範例程式碼建立了二個快取項目,並且它們之間建立依賴關係。具體實現如下: 複製代碼 代碼如下:// Creates cache object Key1.
Cache["Key1"] = "Cache Item 1";

// Makes Cache["Key2"] dependent on Cache["Key1"].
string[] dependencyKey = new string[1];
dependencyKey[0] = "Key1";

// Creates a CacheDependency object.
CacheDependency dependency = new CacheDependency(null, dependencyKey);

// Establishs dependency between cache Key1 and Key2.
Cache.Insert("Key2", "Cache Item 2", dependency);

現在,當Key1快取項目更新或從緩衝中刪除,Key2快取項目就會自動從緩衝刪除。

檔案依賴

前面我們介紹了快取項目之間的依賴關係,ASP.NET Cache還提供快取項目與檔案之間的依賴關係,當檔案被更新或刪除對應的快取項目也將失效。

在上篇博文《Ajax與JSON的一些總結》的最後介紹的一個DEMO——Weibo Feed中,我們通過即時方式向新浪微博API發送請求擷取相應的資料,但在一定時間內請求的次數是有限制的,一旦超出了限制次數就不再接受請求了(具體請參考Rate-limiting)。所以可以通過Cache的方式把資料緩衝起來,當用戶端請求時,如果快取資料已經存在那麼直接返回資料,否則重新想微博API請求資料。

首先,我們建立一個HttpHandler,它負責向微博API發送請求並且把資料儲存的檔案中,最後把資料返回的用戶端。

圖7 請求流程
接下來,我們定義CacheData()方法把微博資料儲存到文字檔中並且建立緩衝與資料檔案的依賴關係。 複製代碼 代碼如下:/// <summary>
/// Caches the data into text file.
/// </summary>
/// <param name="context">The http context</param>
private void CacheData(HttpContext context)
{
// Weibo API.
string uri = context.Request.QueryString["api"] + "?" +
"source=" + context.Request.QueryString["source"] + "&" +
"count=" + context.Request.QueryString["count"];
HttpWebResponse response = this.GetWeibos(uri);
if (null == response)
{
throw new ArgumentNullException("Response is null");
}
string jsonData;
// Writes the reponse data into text file.
using (var reader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding(response.CharacterSet)))
{
jsonData = reader.ReadToEnd();
}
string dataPath = context.Server.MapPath("weibo.json");
using (var writer = new StreamWriter(dataPath, false, Encoding.GetEncoding(response.CharacterSet)))
{
writer.Write(jsonData);
}
// Establishs dependency between cache weibo and text file.
// Sets cache expires after 2 minuntes.
HttpRuntime.Cache.Insert("weibo", jsonData, Dep, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(2));
}

現在我們把資料儲存到文字檔中並且建立了緩衝weibo與資料檔案的依賴關係,接下來我們要把JSON格式資料返回給用戶端。 複製代碼 代碼如下:/// <summary>
/// Responses the weibo data.
/// </summary>
/// <param name="context">The http contex.</param>
private void ResponseWeibo(HttpContext context)
{
// Gets the weibo cache data.
byte[] buf = Encoding.UTF8.GetBytes(HttpRuntime.Cache["weibo"].ToString());
// Writes the data into output stream.
context.Response.OutputStream.Write(buf, 0, buf.Length);
context.Response.OutputStream.Flush();
////context.Response.Close();
}

上面我們把JSON格式字串轉換為Byte數值,然後寫入到OutputStream中,最後把資料返回給用戶端。 複製代碼 代碼如下:// The function to get weibo data.
loadWeibo: function() {
$.ajax({
// Weibo API.
url: "WeiboHandler.ashx",
type: "GET",
// NOTE: We get the data from same domain,
// dataType is json.
dataType: "json",
data: {
source: JQWeibo.appKey,
count: JQWeibo.numWeibo
},
// When the requet completed, then invokes success function.
success: function(data, textStatus, xhr) {
// Sets html structure.
var html =
'<div class="weibo">' +
'<a href="http://weibo.com/DOMAIN" target="_blank">USER</a>' +
':WEIBO_TEXT<div class="time">AGO</div>';
// Appends weibos into html page.
for (var i = 0; i < data.length; i++) {
$(JQWeibo.appendTo).append(
html.replace('WEIBO_TEXT', JQWeibo.ify.clean(data[i].text))
// Uses regex and declare DOMAIN as global, if found replace all.
.replace(/DOMAIN/g, data[i].user.domain)
.replace(/USER/g, data[i].user.screen_name)
.replace('AGO', JQWeibo.timeAgo(data[i].created_at))
);
}
}
})
}

圖8請求結果
1.1.3 總結
緩衝可以使應用程式的效能得到很大的提高,因此在設計應用程式應該予以考慮,本博文主要介紹了ASP.NET中輸出緩衝和資料緩衝的應用場合和區別。

頁面緩衝適用於產生的頁面通常都相同或改變時間比較固定情況,例如:資料在每小時都會更新,那麼我們可以設定duration為3600s。

資料緩衝適用產生的頁面總是在變化情況。

http://www.codeproject.com/Articles/29899/Exploring-Caching-in-ASP-NET
http://msdn.microsoft.com/zh-cn/library/aa478965.aspx#XSLTsection129121120120
http://www.amazon.com/Beginning-ASP-NET-3-5-2008-Professional/dp/1590598911

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.