在Web应用程序中的使用缓存位置主要有:客户端浏览器缓存、客户端和服务器中以及服务器端,因此缓存可以分为以下几类:

摘要

最近我们的系统面临着严峻性能瓶颈问题,这是由于访问量增加,客户端在同一时间请求增加,这迫使我们要从两个方面解决这一问题,增加硬件和提高系统的性能。

大家可以通过各种各样的方法去优化我们系统,本篇博文将介绍通过Cache方法来优化系统的性能,减轻系统的负担。

正文

不同位置的缓存

在Web应用程序中的使用缓存位置主要有:客户端浏览器缓存、客户端和服务器中以及服务器端,因此缓存可以分为以下几类:

客户端缓存(Client Caching);代理缓存(Proxy Caching);反向代理缓存(Reverse Proxy Caching);服务器缓存(Web Server Caching)

ASP.NET中的缓存

ASP.NET中有两种缓存类型:输出缓存数据缓存

输出缓存:这是最简单的缓存类型,它保存发送到客户端的页面副本,当下一个客户端发送相同的页面请求时,此页面不会重新生成(在缓存有限期内),而是从缓存中获取该页面;当然由于缓存过期或被回收,这时页面会重新生成。

数据缓存

除此之外,还有两个特殊的缓存:片段缓存数据源缓存

片段缓存:这是一种特殊的输出缓存,它不是缓存整个页面,而是缓存部分页面;由于缓存整个页面通常并不可行,因为页面的某些部分是针对用户定制的(例如用户登陆信息),但我们可以把应用程序中共享的部分进行缓存,这时我们可以考虑使用片段缓存和用户控件缓存。

数据源缓存:是建立在数据源控件的缓存,它包括SqlDataSource、ObjectDataSource和XmlDataSource控件。数据源缓存使用数据缓存方式,不同的是我们不需要通过显示方法处理缓存;我们只需设置相应的属性,然后数据源控件就能存储和检索数据。

输出缓存

输出缓存可以把最终呈现的页面缓存起来,当客户端再次请求同一页面时,控制对象不再重新创建,页面的生命周期不再启动,无需再次执行代码,通过在缓存中获取缓存的页面。

现在我们设计一个页面,每当用户发送页面请求时,就获取当前代码执行的时间,然后显示在页面上。

ASP.NET Cache的一些总结1_基于Lucene.net全文检索

图1输出缓存

这是再简单不过的例子,每当用户发送页面请求都会更新页面显示的时间,这是由于每次请求都获取了一个新的页面,实际情况中,我们并不需要实时的响应用户每个页面请求,我们可以通过输出缓存把页面缓存起来每当用户发送同一页面请求时,而且在缓存有效期间,可以通过输出缓存把缓存的页面返回给用户。

我们要实现输出缓存,只需在页面中添加如下代码:

  1. <!-- Adds OutputCache directive --> 
  2. <%@ OutputCache Duration="23" VaryByParam="None" %> 

它支持五个属性,其中两个属性Duration和VaryByParam是必填的

ASP.NET Cache的一些总结1_基于Lucene.net全文检索_02

表1输出缓存属性

这里我们把输出缓存的有效期设置为23秒,也就是说,当缓存超过有效期就会被回收;当用户再次请求该页面时,就要重新创建页面。

客户端缓存

另一种选择是客户端缓存,如果用户在浏览器中点击“后退”按钮或在地址栏中重新输入URL,那么在这种情况下,浏览器将从缓存获取页面;然而,如果用户点击“刷新”按钮,那么浏览器中缓存将失效,浏览器发送页面请求。

如果我们要使用客户端缓存,只需指定OutputCache中的属性Location=”Client”就OK了,具体代码如下所示:

  1. <!-- Sets client OutputCache --> 
  2. <%@ OutputCache Duration="23" VaryByParam="None" Location="Client" %> 

通过在OutputCache中添加Location属性,我们实现了客户端缓存,通过设置客户端缓存我们能够减少的客户端请求,也许有人会问:“每个用户第一次页面请求都需要服务器来完成,这不能很好的减少服务的压力”。的确是这样,相对于服务器缓存,客户端缓存并没有减少代码的执行和数据库的操作,但是当我们把包含个性化数据的页面缓存在服务器中,客户端请求页面时,由于不同的用户个性化数据不同,这将会导致请求出现错误,所以我们可以使用片段缓存把公用的部分缓存起来或客户端缓存把用户信息缓存起来。

Query String缓存

在前面的例子中,我们把OutputCache中的VaryByParam属性设置为None,那么ASP.NET程序只缓存一个页面副本;如果页面请求包含查询参数,那么在缓存的有效期内,我们只可以查看到只是缓存结果,假设我们有个报表程序,它提供用户根据产品名称查询相关的产品信息。

首先我们创建两个页面:查询和结果页面,由于时间关系我们已经把页面设计好了,具体如下所示:

ASP.NET Cache的一些总结1_基于Lucene.net全文检索_03

ASP.NET Cache的一些总结1_基于Lucene.net全文检索_04图2报表程序

首先我们提供查询页面,让用户根据成品名称(ProductName)查询相应的成品信息,具体的代码如下:

  1. protected void Page_Load(object sender, EventArgs e)  
  2. {  
  3.     if (!Page.IsPostBack)  
  4.     {  
  5.         // Gets product id from table Production.Product.  
  6.         // Then binding data to drop down list control.  
  7.         InitControl(GetProductId());  
  8.     }  
  9. }  
  10. /// <summary>  
  11. /// Handles the Click event of the btnSubmit control.  
  12. /// Redirects to relative product information page.  
  13. /// </summary>  
  14. protected void btnSubmit_Click(object sender, EventArgs e)  
  15. {  
  16.     Response.Redirect(string.Format("Product.aspx?productname={0}", ddlProductName.SelectedValue));  

当用户点击Submit按钮后,跳转到Product页面并且在Url中传递查询参数——产品名称(ProducName)。

接下来,我们继续完成查询页面,由于在前一页面中传递了查询参数ProductName,那么我们将根据ProductName查询数据库获取相应的产品信息,具体代码如下所示:

  1. protected void Page_Load(object sender, EventArgs e)  
  2. {  
  3.     // Get product name.  
  4.     string productName = Request.QueryString["productname"];  
  5.  
  6.     // Binding data to data grid view control.  
  7.     InitControl(this.GetData(productName));  
  8. }  
  9.  
  10. /// <summary>  
  11. /// Inits the control.  
  12. /// </summary>  
  13. /// <param name="ds">The dataset.</param>  
  14. private void InitControl(DataSet ds)  
  15. {  
  16.     dgvProduct.DataSource = ds;  
  17.     dgvProduct.DataBind();  
  18. }  
  19.  
  20. /// <summary>  
  21. /// Gets the data.  
  22. /// </summary>  
  23. /// <param name="productName">Name of the product.</param>  
  24. /// <returns>Returns dataset</returns>  
  25. private DataSet GetData(string productName)  
  26. {  
  27.     // The query sql base on product name.  
  28.     string sql =   
  29.         string.Format(  
  30.         "SELECT Name, ProductNumber, SafetyStockLevel, ReorderPoint, StandardCost, DaysToManufacture " 
  31.         + "FROM Production.Product WHERE ProductNumber='{0}'",  
  32.         productName);  
  33.  
  34.     // Get data from table Production.Product.  
  35.     using (var con = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLCONN"].ToString()))  
  36.     using (var com = new SqlCommand(sql, con))  
  37.     {  
  38.         com.Connection.Open();  
  39.         ////gdvData.DataSource = com.ExecuteReader();  
  40.         ////gdvData.DataBind();  
  41.         var ada = new SqlDataAdapter(com);  
  42.         var ds = new DataSet();  
  43.         ada.Fill(ds);  
  44.         return ds;  
  45.     }  

前面示例,我们通过Request的属性QueryString获取ProductName的值,然后根据ProductName查询数据库,最后把获取数据绑定到Datagridview控件中(注:前面实例没有考虑SQL Injection问题)。

ASP.NET Cache的一些总结1_基于Lucene.net全文检索_05

图3查询结果

现在我们在页面中添加输出缓存,如下代码:

  1. <!-- Adds OutputCache directive --> 
  2. <%@ OutputCache Duration="30" VaryByParam="None" %> 

前面提到当输出缓存的属性VaryByParam=”None”时,ASP.NET程序在缓存有效期内只缓存一个页面副本;现在我们在缓存有效期内(30s)再发送请求。

ASP.NET Cache的一些总结1_基于Lucene.net全文检索_06

图4查询结果

通过上图我们发现,现在查询参数ProductName=BK-M18B-40,但查询结果依然是ProductName=BB-9108的数据,这是由于ASP.NET程序在缓存有效期内只缓存一个页面副本。

通过上面的示例,我们发现只缓存一个页面是不适用包含查询参数的页面输出缓存;其实前面的示例我们只需稍稍改动就能符合查询参数的情况了,想必大家已经知道了,只需把VaryByParam属性设置为“*”就OK了。

ASP.NET Cache的一些总结1_基于Lucene.net全文检索_07

ASP.NET Cache的一些总结1_基于Lucene.net全文检索_08图5查询结果

现在查询可以获取相应的结果,如果查询参数和前一个请求相同并且该页面缓存有效,那么缓存将被重用,否则,创建一个新的页面缓存。

由于ASP.NET给每个查询参数都添加了输出缓存,但我们要注意的是是否真的有必要缓存每个查询参数都缓存一个页面副本,假设查询Url中增加一个参数参数ProductId,那么现在Url中就有两个查询参数了(ProductName和ProductId)。

前面我们把VaryByParam设置为“*”,所为ASP.NET程序对ProductName和ProductId都创建页面缓存,如果我们只针对ProductName创建页面缓存,这时我们可以修改VaryByParam,具体如下所示:

  1. <!-- Sets VaryByParam property--> 
  2. <%@ OutputCache Duration="30" VaryByParam="productname" %>