缓存

一、以下介绍中的数据库缓存依赖(第7部分)方法是使用轮询的方式进行缓存失效检查,适用于Sql server 2000及更高版本

1、简介

时候总听到网友说网站运行好慢,不知如何是好;有时候也总见到一些朋友写的网站功能看起来非常好,但访问性能却极其的差。没有“勤俭节约”的意识,势必会造成“铺张浪费”。如何应对这种情况,充分利用系统缓存则是首要之道。

     系统缓存有什么好处呢?举个简单的例子,你想通过网页查询某些数据,而这些数据并非实时变化,或者变化的时间是有期限的。例如查询一些历史数据。那么每个用户每次查的数据都是一样的。如果不设置缓存,ASP.NET也会根据每个用户的请求重复查询n次,这就增加了不必要的开销。所以,可能的情况下尽量使用缓存,从内存中返回数据的速度始终比去数据库查的速度快,因而可以大大提供应用程序的性能。毕竟现在内存非常便宜,用空间换取时间效率应该是非常划算的。尤其是对耗时比较长的、需要建立网络链接的数据库查询操作等。

缓存功能是大型网站设计一个很重要的部分。由数据库驱动的Web应用程序,如果需要改善其性能,最好的方法是使用缓存功能。

2、系统缓存的概述

15.4.1      缓存的分类 

     从分布上来看,我们可以概括为客户端缓存和服务器端缓存。

客户端缓存——这点大家都有直观的印象。比如你去一个新的网站,第一次可能要花一阵子时间才能载入整个页面。而以后再去呢,时间就会大大的缩短,原因就在于这个客户端缓存。现在的浏览器都比较智能,它会在客户机器的硬盘上保留许多静态的文件,比如各种gif,jpeg文件等等。等以后再去的时候,它会尽量使用本地缓存里面的文件。只有服务器端的文件更新了,或是缓存里面的文件过期了,它才会再次从服务器端下载这些东西。很多时候是IE替我们做了这件事情。

  

服务器端缓存——有些东西没法或是不宜在客户端缓存,那么我们只好在服务器端想想办法了。服务器端缓存从性质上看,又可以分为两种。 

(1)静态文件缓存 

    好多页面是静态的,很少改动,那么这种文件最适于作静态缓存。现在的IIS 6.0这部分内容是直接存放在Kernel的内存中,由HTTP.SYS直接管理。由于它在Kernel Space,所以它的性能非常的高。用户的请求如果在缓存里面,那么HTTP.SYS直接将内容发送到network driver上去,不需要像以前那样从IISUser space的内存copyKernel中,然后再发送到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;

3、页面输出缓存

页面输出缓存是最为简单的缓存机制,该机制将整个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> 

                <add name="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秒,并且页面不随任何GETPOST参数改变(即使不使用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秒,并且页面不随任何GETPOST参数改变,等同于“<%@ 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枚举值,包括NoCachePrivatePublicServerServerAndNoCacheServerAndPrivate(有关这些枚举值的定义,可参考MSDN)。另一种方法的参数有两个,一个参数是HttpCacheability枚举值,另一个参数是字符串,表示添加到标头的缓存控制扩展。需要注意的是,仅当与PrivateNoCache指令一起使用时,字段扩展名才有效。如果组合不兼容的指令和扩展,则此方法将引发无效参数异常。 

4、页面局部缓存

有时缓存整个页面是不现实的,因为页的某些部分可能在每次请求时都需要变化。在这些情况下,只能缓存页的一部分。顾名思义,页面部分缓存是将页面部分内容保存在内存中以便响应用户请求,而页面其他部分内容则为动态内容。页面部分缓存的实现包括两种方式:控件缓存和替换后缓存。 

1. 控件缓存(也称为片段缓存) 

    这种方式允许将需要缓存的信息包含在一个用户控件内,然后,将该用户控件标记为可缓存的,以此来缓存页面输出的部分内容。该选项允许缓存页面中的特定内容,而没有缓存整个页面,因此,每次都需重新创建整个页。例如,如果要创建一个显示大量动态内容(如股票信息)的页,其中有些部分为静态内容(如每周总结),这时可以将静态部分放在用户控件中,并允许缓存这些内容。 

    ASP.NET中,提供了UserControl这种用户控件的功能。一个页面可以通过多个UserControl来组成。只需要在某个或某几个UserControl里设置缓存。 

    例如:

    那么可以在WebUserControl1.ascx的页头代码中添加声明语句:

 

<%@ControlLanguage="C#"AutoEventWireup="true"CodeBehind="WebUserControl1.ascx.cs"Inherits="CacheWebApp._16_4_5.WebUserControl1"%>

<%@OutputCacheDuration="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属性值指定的方法返回值显示在页面上,因此,显示的是当前最新时间。  

随着页面的刷新,真实时间在变,而页面缓存的时间在指定的缓存时间内始终不变。 

注意: 

Substitution控件无法访问页上的其他控件,也就是说,无法检查或更改其他控件的值。但是,代码确实可以使用传递给它的参数来访问当前页上下文。 

在缓存页包含的用户控件中可以包含Substitution控件。但是,在输出缓存用户控件中不能放置Substitution控件。 

Substitution控件不会呈现任何标记,其位置所显示内容完全取决于所定义方法的返回字符串。 

 

(2) Substitution控件API应用 

上一小节介绍了以声明方式使用Substitution控件实现缓存后替换的应用。本节说明另一种实现方法。该方法的核心是以编程方式利用Substitution控件API实现缓存后替换,相对于以声明方式使用Substitution控件的方法具有更强灵活性。 

通过为Substitution指定回调方法,实现和声明同样的效果。Substitution的回调方法必须是 

HttpResponseSubstitutionCallback委托定义的方法,它有两个特征: 

一是返回值必须是String 

二是参数有且仅有一个,并且是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调用的回调方法显示的内容是不被缓存的。 

5、应用程序数据缓存

System.Web.Caching 命名空间提供用于缓存服务器上常用数据的类。此命名空间包括 Cache类,该类是一个字典,您可以在其中存储任意数据对象,如哈希表和数据集。它还为这些对象提供了失效功能,并为您提供了添加和移除这些对象的方法。您还可以添加依赖于其他文件或缓存项的对象,并在从 Cache对象中移除对象时执行回调以通知应用程序。 

 

///<summary>

///获取当前应用程序指定CacheKeyCache对象值

///</summary>

///<param name="CacheKey">索引键值</param>

///<returns>返回缓存对象</returns>

public staticobject GetCache(string CacheKey)

{

    System.Web.Caching.Cache objCache =HttpRuntime.Cache;

    return objCache[CacheKey];

}

///<summary>

///设置当前应用程序指定CacheKeyCache对象值

///</summary>

///<param name="CacheKey">索引键值</param>

///<param name="objObject">缓存对象</param>

public staticvoid SetCache(string CacheKey,object objObject)

{

    System.Web.Caching.Cache objCache =HttpRuntime.Cache;

    objCache.Insert(CacheKey, objObject);

}

///<summary>

///设置当前应用程序指定CacheKeyCache对象值

///</summary>

///<param name="CacheKey">索引键值</param>

///<param name="objObject">缓存对象</param>

///<param name="absoluteExpiration">绝对过期时间</param>

///<param name="slidingExpiration">最后一次访问所插入对象时与该对象过期时之间的时间间隔</param>

public staticvoid 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();

}

  

    以上几种方法都很好的解决了数据缓存的问题,但由一个最大的问题是当数据发生变化了,而缓存里还是过期的数据,只有等缓存过期后才会重新获取最新的数据,这样的话,很多时候用户获取的数据都是和实际数据不一致的过期数据。这同样给用户造成了比较大的麻烦,怎么办呢?接着往下看。 

6、文件缓存依赖

这种策略让缓存依赖于一个指定的文件,通过改变文件的更新日期来清除缓存。

                                           

        

///<summary>

///获取当前应用程序指定CacheKeyCache对象值

///</summary>

///<param name="CacheKey">索引键值</param>

///<returns>返回缓存对象</returns>

public staticobject 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 staticvoid 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应用中的缓存依赖于同一个共享的文件,可能会省掉这个麻烦。

7、数据库缓存依赖

更多的时候,我们的服务器性能损耗还是在查询数据库的时候,所以对数据库的缓存还是显得特别重要,上面几种方式都可以实现部分数据缓存功能。但问题是我们的数据有时候是在变化的,这样用户可能在缓存期间查询的数据就是老的数据,从而导致数据的不一致。那有没有办法做到,数据如果不变化,用户就一直从缓存中取数据,一旦数据变化,系统能自动更新缓存中的数据,从而让用户得到更好的用户体验。

   答案是肯定的!.NET已经为我们提供了这样一种非常好的解决方法:SqlCacheDependency数据库缓存依赖。  

实现步骤:

    下面就让我们看一下如何实现数据库缓存依赖功能:

第一步: 修改web.config,让项目启用SqlCacheDependency 。

将下列代码加入web.config的<system.web>节:     

<?xmlversion="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>

    

        <compilationdebug="true">

        </compilation>     

        <authenticationmode="Windows"/>      

    </system.web>

</configuration>

这里的connectionStringName指定了在<connectionStrings>中添加的某一个连接字符串。name则是为该SqlCacheDependency起的名字,这个名字将在第3步中用到。
SqlCacheDependency类会自动完成对此配置节信息的读取以建立和数据库之间的联系。

 

注意:

<databases>节的<addname="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所示:

命令执行后,在指定的数据库中会多出一个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"
如果是Sql验证的话要把-E换成,-U (用户名),-P (密码)

 

以下是该工具的命令参数说明

-? 显示该工具的帮助功能;

-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>

///获取当前应用程序指定CacheKeyCache对象值

///</summary>

///<param name="CacheKey">索引键值</param>

///<returns>返回缓存对象</returns>

public staticobject 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 staticvoid 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 =newSqlConnection(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表的内容发生变化时,查询操作才会重新查询数据更新缓存的内容,可以大大减少数据库的重复查询和提高系统的性能和运行效率。

 

8、第三方分布式缓存解决方案 MemcachedCacheman

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 项目。

目前最新的版本是0.0.2版 :http://www.sriramkrishnan.com/code/

 

转载自  http://www.cnblogs.com/yuhanzhong/archive/2011/08/10/2133284.html

 

 

 二、以下方法是使用查询通知方式进行缓存失效检查,适用于Sql server 2005及更高版本

 

Sql Server 7.0/2000下 SqlCacheDependency使用轮询的方式进行缓存失效检查, 虽然ms说对服务器压力不大, 但还是有一些的, 而且对于不常改动的混存内容无休止的轮询感觉有点浪费, 不很经济.

Sql server 2005/2008下增加使用查询通知方式进行缓存失效检查, 它通过Sql Server内部的消息队列进行异步通知, 这样就大大减轻了服务器的压力, 实现的很经济, 下面就是具体的步骤:

 

1.检测是否已经启用Service Broker

  Select DATABASEpRoPERTYEX('数据库名称','IsBrokerEnabled')  -- 1 表示已经启用 0 表示没有启用

例如

Select DATABASEpRoPERTYEX('SqlDependencyTest','IsBrokerEnabled')

 

2.启用Service Broker                   

  ALTER DATABASE 数据库名称 SET ENABLE_BROKER;       

例如

ALTER DATABASE SqlDependencyTest SET ENABLE_BROKER

 

  注意:如果执行此语句处于假死状态,请重启(restart)数据库,然后什么都别做, 先执行上面启用Service Broker这个语句就行了!

 

3.给您的数据库访问帐号授予权限

  

GRANT SUBSCRIBE QUERY NOTIFICATIONS TO TestUser  --TestUser是访问账号

 

注意:这一步非常重要, 如果没有权限, 数据库改变的通知将无法接收, cache永远都不会被刷新,我开始是用的sa帐号,死活都不刷新,花了我两天时间调试这个问题, 还是无法给sa授此权限(ms禁止), 所以,换个数据库访问帐号即可.

 

 4.确定数据库连接字符串.

您可以在web.config里增加一个数据库连接字符串.

<connectionStrings>
<add name="TestConnectionString" connectionString="Data Source=.;Persist Security Info=True;User ID=TestUser;Password=123456;Initial Catalog=SqlDependencyTest"/>
</connectionStrings>

如果您的应用程序数据库连接字符串如果不存放在web.config中, 也没关系, 这不是必须的,您只要能在下面访问到即可.

 

5.启动SqlDependency监听.

   在Global.ascx里的Application_Start和Application_End里增加如下代码:

 

 

void Application_Start(object sender, EventArgs e) 
    {
        // 在应用程序启动时运行的代码
        string connString = System.Web.Configuration.WebConfigurationManager.ConnectionStrings["TestConnectionString"].ConnectionString;
        System.Data.SqlClient.SqlDependency.Start(connString);
    }
    
    void Application_End(object sender, EventArgs e) 
    {
        //  在应用程序关闭时运行的代码
        string connString = System.Web.Configuration.WebConfigurationManager.ConnectionStrings["TestConnectionString"].ConnectionString;
        System.Data.SqlClient.SqlDependency.Stop(connString);
    }

    注意:System.Data.SqlClient.SqlDependency.Start(connString)可以多次执行,也就是说,除了在这里,您还可以在您程序的代码里加上这句。

 

6.主体程序

从cache或db中获取数据。

private DataSet GetData()
    {
        if (HttpRuntime.Cache["EntityResourceCollection"] != null)
        {
            //get entity resource collection from cache
            return (DataSet)HttpRuntime.Cache["EntityResourceCollection"];
        }
        else
        {
            //get from database
            DataSet ds = new DataSet();

            using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["TestConnectionString"].ConnectionString))
            {
                string sql = " Select ID,Name From dbo.[User]";

                using (SqlCommand command = new SqlCommand(sql, connection))
                {
                    SqlCacheDependency dependency = new SqlCacheDependency(command);

                    using (SqlDataAdapter adapter = new SqlDataAdapter()) //查询数据
                    {
                        adapter.SelectCommand = command;
                        adapter.Fill(ds);
                    }

                    HttpRuntime.Cache.Insert("EntityResourceCollection", ds, dependency);
                }

            }
            return ds;
        }

    }

  注意: string sql = " Select ID,Name From dbo.[User]";语句的要求非常严格。必须按SQL Server Service Broker提供制定规则的查询语句,一般来讲,必须是简单的sql查询语句(不能用*,不能用top,不能用函数,包括聚合函数,不能用子查询,包括where后的子查询,不能用外连接,自连接,不能用临时表,不能用变量,不能用视图,不能垮库,表名之前必须加类似dbo数据库所有者这样的前缀)例如:select * from table1,select column1 from table1,select count(*) from table1 都是错误的sql查询语句,select column1 from dbo.table1 则是正确的语句。 

基本上就是这6步。

 

注意, 如果您的代码中使用了SqlCacheDependencyAdmin.EnableNotifications(connectionString), 或者SqlCacheDependency是使用(库名,表名)方式构建的 SqlCacheDependency scd = new SqlCacheDependency("数据库名称","表名"),这说明,您仍然在使用sqlserver 7.0/2000 轮询的方式进行缓存依赖检查,虽然在sql 2005/2008里运行没问题,但有些不经济。  

 

转载自 http://space.itpub.net/16436858/viewspace-630489   

注:代码中的数据库和账号名称改为了自己测试时所用的;

 

其他参考:http://www.cnblogs.com/over140/archive/2009/01/15/1376318.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值