ASP.NET的缓存

来源:互联网 发布:在太空看不到长城知乎 编辑:程序博客网 时间:2024/04/29 16:57

http://blog.csdn.net/takeie/archive/2007/07/24/1706087.aspx讲得更详细,这里只是个人心得

 

1   ASP.NET提供两种基本的缓存机制提供缓存功能。一是应用程序缓存,另一种是页输出缓存

 

1.1   应用程序缓存的使用

 using System.Web.Caching

 

        //添加数据项到应用程序缓存中

        Cache["First"] = "1st Item";

        Cache.Insert("Second", "2nd Item");

 

        //为应用程序添加依赖项

        string[] dependencies = { "Second" };

        Cache.Insert("Third", "3rd Item", new CacheDependency(null, dependencies));

 

        //手动移除相关数据项

        Cache.Remove("First");

1.2  页输出缓存,分为整页缓存和部分页缓存

主要包含两个参数   Duration:用于设置页面或控件进行缓存的时间(秒)

              VaryByParam:可以根据设置的参数值建立不同的缓存

       //TextBox1控件建立缓存,过期时间为60

       <%@ OutputCache Duration="60" VaryByParam="TextBox1" %>

 

缓存机制的引用可以提高3倍多的性能,但缓存的数据过期问题在某些场景中甚至比低效的性能能更让人难以接受。

 

2.   .Net 2.0 中引入自定义缓存依赖项,特别针对于SQL SERVERSqlCacheDependency,可以避免数据过期的问题,它能够根据数据库中相应数据的变化,通知缓存

,并移除过期数据。同样,SqlCacheDependency也在System.Web.Caching命名空间下。

 

SQL SERVER 2005默认就支持SqlCacheDependency

SQL SERVER 2000要支持就必须进行相关配置,两种方法使用aspnet_regsql命令行工具,或者使用SqlCacheDependencyAdmin类。

 

2.1   使用aspnet_regsql

命令参数说明:

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

-S  后接的参数为数据库服务器的名称或者IP地址;

-U  后接的参数为数据库的登陆用户名;

-P  后接的参数为数据库的登陆密码;

-E  当使用windows集成验证时,使用该功能;

-d  后接参数为对哪一个数据库采用SqlCacheDependency功能;

-t  后接参数为对哪一个表采用SqlCacheDependency功能;

-ed  允许对数据库使用SqlCacheDependency功能;

-dd  禁止对数据库采用SqlCacheDependency功能;

-et  允许对数据表采用SqlCacheDependency功能;

-dt  禁止对数据表采用SqlCacheDependency功能;

-lt  列出当前数据库中有哪些表已经采用sqlcachedependency功能。

 

例如在Visual Studio 2005 命令提示输入aspnet_regsql -S localhost -E -d Test1-ed

说明将对名为Test1的数据库采用SqlCacheDependency功能,且SQL Server采用了windows集成验证方式.

输入完后,会自动在Test1数据库中建立名为AspNet_SqlCacheTablesForChangeNotification的表.

       字段tableName记录要追踪的数据表的名称。

       notificationCreated字段记录开始追踪的时间。

       changeId作为一个类型为int的字段,用于记录数据表数据发生变化的次数

输入aspnet_regsql -S localhost -E -d Test1 -t TestMain -et表示对TestMain

除此之外,执行该命令还会为Test1数据库添加一组存储过程,为ASP.NET提供查询追踪的数据表的情况,同时还将为使用了SqlCacheDependency的表添加触发器,分别对应InsertUpdateDelete等与数据更改相关的操作。

Test1表的触发器

Create  TRIGGER dbo.[TestMain_AspNet_SqlCacheNotification_Trigger] ON [TestMain]

                       FOR INSERT, UPDATE, DELETE AS BEGIN

                       SET NOCOUNT ON

                       EXEC dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure N'TestMain'

                       END

 

其中,AspNet_SqlCacheUpdateChangeIdStoredProcedure即是工具添加的一组存储过程中的一个。当对Test1数据表执行InsertUpdateDelete等操作时,就会激活触发器,然后执行AspNet_SqlCacheUpdateChangeIdStoredProcedure存储过程。其执行的过程就是修改AspNet_SqlCacheTablesForChangeNotification数据表的changeId字段值

 

2.2         利用SqlCacheDependencyAdmin

我们也可以利用编程的方式来来管理数据库对SqlCacheDependency特性的使用。该类包含了五个重要的方法:

DisableNotifications

为特定数据库禁用 SqlCacheDependency对象更改通知

DisableTableForNotifications

为数据库中的特定表禁用SqlCacheDependency对象更改通知

EnableNotifications

为特定数据库启用SqlCacheDependency对象更改通知

EnableTableForNotifications

为数据库中的特定表启用SqlCacheDependency对象更改通知

GetTablesEnabledForNotifications

返回启用了SqlCacheDependency对象更改通知的所有表的列表

假设我们定义了如下的数据库连接字符串:
      const string connectionStr = "Server=localhost;Database=Test1";那么为数据库Test1启用SqlCacheDependency对象更改通知的实现为:

        protected void Page_Load(object sender, EventArgs e)

        {    

           if (!IsPostBack)

           {      

                SqlCacheDependencyAdmin.EnableNotifications(connectionStr);   

           }

              }

为数据表TestMain启用SqlCacheDependency对象更改通知的实现则为:
SqlCacheDependencyAdmin.EnableTableForNotifications(connectionStr, "TestMain");

虽然说编程方式赋予了程序员更大的灵活性,但aspnet_regsql工具却提供了更简单的方法实现对SqlCacheDependency的配置与管理。PetShop 4.0采用的正是aspnet_regsql工具的办法,它编写了一个文件名为InstallDatabases.cmd的批处理文件,其中包含了对aspnet_regsql工具的执行,并通过安装程序去调用该文件,实现对SQL Server的配置。

3.       PetShop 4.0ASP.NET缓存的实现

3.1  CacheDependency接口

 PetShop 4.0引入了SqlCacheDependency特性,对CategoryProductItem数据表对应的缓存实施了SQL Cache Invalidation技术。当对应的数据表数据发生更改后,该技术能够将相关项从缓存中移除。实现这一技术的核心是SqlCacheDependency类,它继承了CacheDependency类。然而为了保证整个架构的可扩展性,我们也允许设计者建立自定义的CacheDependency类,用以扩展缓存依赖。这就有必要为CacheDependency建立抽象接口,并在web.config文件中进行配置。

PetShop 4.0的命名空间PetShop.ICacheDependency中,定义了名为IPetShopCacheDependency接口,它仅包含了一个接口方法:
  public interface IpetShopCacheDependency

 {

        AggregateCacheDependency GetDependency();

  }

 

AggregateCacheDependency.Net Framework 2.0新增的一个类,它负责监视依赖项对象的集合。当这个集合中的任意一个依赖项对象发生改变时,该依赖项对象对应的缓存对象都将被自动移除。
AggregateCacheDependency
类起到了组合CacheDependency对象的作用,它可以将多个CacheDependency对象甚至于不同类型的CacheDependency对象与缓存项建立关联。由于PetShop需要为CategoryProductItem数据表建立依赖项,因而IPetShopCacheDependency的接口方法GetDependency()其目的就是返回建立了这些依赖项的AggregateCacheDependency对象。

 

3.2      CacheDependency实现

CacheDependency的实现正是为CategoryProductItem数据表建立了对应的SqlCacheDependency类型的依赖项,如代码所示:

    public abstract class TableDependency : PetShop.ICacheDependency.IPetShopCacheDependency {

 

        protected char[] configurationSeparator = new char[] { ',' };

 

        protected AggregateCacheDependency dependency = new AggregateCacheDependency();

 

        protected TableDependency(string configKey) {

 

            string dbName = ConfigurationManager.AppSettings["CacheDatabaseName"];

            string tableConfig = ConfigurationManager.AppSettings[configKey];

            string[] tables = tableConfig.Split(configurationSeparator);

 

            foreach (string tableName in tables)

                dependency.Add(new SqlCacheDependency(dbName, tableName));

        }

 

        public AggregateCacheDependency GetDependency()

              {

            return dependency;

        }

需要建立依赖项的数据库与数据表都配置在web.config文件中,其设置如下:

              <add key="CacheDatabaseName" value="MSPetShop4"/>

              <add key="CategoryTableDependency" value="Category"/>

              <add key="ProductTableDependency" value="Product,Category"/>

              <add key="ItemTableDependency" value="Product,Category,Item"/>

根据各个数据表间的依赖关系,因而不同的数据表需要建立的依赖项也是不相同的,从配置文件中的value值可以看出。然而不管建立依赖项的多寡,其创建的行为逻辑都是相似的,因而在设计时,抽象了一个共同的类TableDependency,并通过建立带参数的构造函数,完成对依赖项的建立。由于接口方法GetDependency()的实现中,返回的对象dependency是在受保护的构造函数创建的,因此这里的实现方式也可以看作是Template Method模式的灵活运用。例如TableDependency的子类Product,就是利用父类的构造函数建立了ProductCategory数据表的SqlCacheDependency依赖:

    public class Product : TableDependency

    {

        public Product() : base("ProductTableDependency") { }

}

如果需要自定义CacheDependency,那么创建依赖项的方式又有不同。然而不管是创建SqlCacheDependency对象,还是自定义的CacheDependency对象,都是将这些依赖项添加到AggregateCacheDependency类中,因而我们也可以为自定义CacheDependency建立专门的类,只要实现IPetShopCacheDependency接口即可。

3.3      CacheDependency工厂

继承了抽象类TableDependencyProductCategoryItem类均需要在调用时创建各自的对象。由于它们的父类TableDependency实现了接口IPetShopCacheDependency,因而它们也间接实现了IPetShopCacheDependency接口,这为实现工厂模式提供了前提。

PetShop 4.0中,依然利用了配置文件和反射技术来实现工厂模式。命名空间PetShop.CacheDependencyFactory中,类DependencyAccess即为创建IPetShopCacheDependency对象的工厂类:

    public static class DependencyAccess

    {

        public static IPetShopCacheDependency CreateCategoryDependency()

        {

            return LoadInstance("Category");

        }

 

        public static IPetShopCacheDependency CreateProductDependency()

        {

            return LoadInstance("Product");

        }

 

        public static IPetShopCacheDependency CreateItemDependency()

        {

            return LoadInstance("Item");

        }

        

        private static IPetShopCacheDependency LoadInstance(string className)

        {

            string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];

            string fullyQualifiedClass = path + "." + className;

 

            return (IPetShopCacheDependency)Assembly.Load(path).CreateInstance(fullyQualifiedClass);

        }

   }

虽然DependencyAccess类创建了实现了IPetShopCacheDependency接口的类CategoryProductItem,然而我们之所以引入IPetShopCacheDependency接口,其目的就在于获得创建了依赖项的AggregateCacheDependency类型的对象。我们可以调用对象的接口方法GetDependency(),如下所示:

AggregateCacheDependency dependency = DependencyFacade.GetCategoryDependency();

 

比起直接调用DependencyAccess类的GetDependency()方法而言,除了方法更简单之外,同时它还对CacheDependencyAssembly配置节进行了判断,如果其值为空,则返回null对象。

 

PetShop.WebApp_Code文件夹下,静态类WebUtilityGetCategoryName()GetProductName()方法调用了DependencyFacade类。例如GetCategoryName()方法:

        public static string GetCategoryName(string categoryId)

        {

            Category category = new Category();

            if (!enableCaching)

                return category.GetCategory(categoryId).Name;

 

            string cacheKey = string.Format(CATEGORY_NAME_KEY, categoryId);

 

            // 检查缓存中是否存在该数据项;

            string data = (string)HttpRuntime.Cache[cacheKey];

            if (data == null)

            {

                // 通过web.config的配置获取duration;

                int cacheDuration = int.Parse(ConfigurationManager.AppSettings["CategoryCacheDuration"]);

                // 如果缓存中不存在该数据项,则通过业务逻辑层访问数据库获取;

                data = category.GetCategory(categoryId).Name;

                // 通过Facade类创建AggregateCacheDependency对象;

                AggregateCacheDependency cd = DependencyFacade.GetCategoryDependency();

                // 将数据项以及AggregateCacheDependency 对象存储到缓存中;

                HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);

            }

            return data;

        }

GetCategoryName()方法首先会检查缓存中是否已经存在CategoryName数据项,如果已经存在,就通过缓存直接获取数据;否则将通过业务逻辑层调用数据访问层访问数据库获得CategoryName,在获得了CategoryName后,会将新获取的数据连同DependencyFacade类创建的AggregateCacheDependency对象添加到缓存中。

 

WebUtility静态类被表示层的许多页面所调用,例如Product页面:

    public partial class Products : System.Web.UI.Page

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            //get page header and title

            Page.Title = WebUtility.GetCategoryName(Request.QueryString["categoryId"]);

        }

}

显示页面title的逻辑是放在Page_Load事件方法中,因而每次打开该页面都要执行获取CategoryName的方法。如果没有采用缓存机制,当Category数据较多时,页面的显示就会非常缓慢。

3.3 代理模式

业务逻辑层BLL中与ProductCategoryItem有关的业务方法,其实现逻辑是调用数据访问层(DAL)对象访问数据库,以获取相关数据。为了改善系统性能,我们就需要为这些实现方法增加缓存机制的逻辑。当我们操作增加了缓存机制的业务对象时,对于调用者而言,应与BLL业务对象的调用保持一致。也即是说,我们需要引入一个新的对象去控制原来的BLL业务对象,这个新的对象就是Proxy模式中的代理对象。

public static class ProductDataProxy

    {

        private static readonly int productTimeout = int.Parse(ConfigurationManager.AppSettings["ProductCacheDuration"]);

        private static readonly bool enableCaching = bool.Parse(ConfigurationManager.AppSettings["EnableCaching"]);

 

        public static ProductInfo GetProduct(string productId)

        {

            Product product = new Product();

 

            if (!enableCaching)

                return product.GetProduct(productId);

 

            string key = "product_" + productId;

            ProductInfo data = (ProductInfo)HttpRuntime.Cache[key];

 

            // 检查数据是否在缓存里

            if (data == null)

            {

                //如果不存在从业务逻辑层里取出数据

                data = product.GetProduct(productId);

 

                // 从工厂中创建一个AggregateCacheDependency对象

                AggregateCacheDependency cd = DependencyFacade.GetProductDependency();

 

                // 存入缓存,并创建一个AggregateCacheDependency对象                HttpRuntime.Cache.Add(key, data, cd, DateTime.Now.AddHours(productTimeout), Cache.NoSlidingExpiration, CacheItemPriority.High, null);

            }

            return data;

        }

    }

引入Proxy模式,实现了在缓存级别上对业务对象的封装,增强了对业务对象的控制。由于暴露在对象外的方法是一致的,因而对于调用方而言,调用代理对象与真实对象并没有实质的区别。

如果需要对UI层采用缓存机制,将应用程序数据存放到缓存中,就可以调用这些代理对象。以ProductsControl用户控件为例,调用方式如下:
productsList.DataSource = ProductDataProxy.GetProductsByCategory(categoryKey);

PetShop 4.0的设计中,对于类似于ProductsControl类型的控件而言,采用的缓存机制是页输出缓存。我们可以从ProductsControl.ascx页面的Source代码中发现

<%@ OutputCache Duration="100000" VaryByParam="page;categoryId" %>

ASP.NET 1.x的页输出缓存不同的是,在ASP.NET 2.0中,为ASP.NET用户控件新引入了CachePolicy属性,该属性的类型为ControlCachePolicy类,它以编程方式实现了对ASP.NET用户控件的输出缓存设置。我们可以通过设置ControlCachePolicy类的Dependency属性,来设置与该用户控件相关的依赖项,例如在ProductsControl用户控件中,进行如下的设置:

        protected void Page_Load(object sender, EventArgs e)

{

           this.CachePolicy.Dependency = DependencyFacade.GetProductDependency();

    }

采用页输出缓存,并且利用ControlCachePolicy设置输出缓存,能够将业务数据与整个页面放入到缓存中。这种方式比起应用程序缓存而言,在性能上有很大的提高。同时,它又通过引入的SqlCacheDependency特性有效地避免了数据过期的缺点,因而在PetShop 4.0中被广泛采用。相反,之前为ProductCategoryItem业务对象建立的代理对象则被投闲散置,仅仅作为一种设计方法的展示而幸存与整个系统的源代码中。

 

 

原创粉丝点击