PetShop 4.0的缓存处理分析

在访问量非常大,但更新较少的网站中使用缓存,可以大大提高程序运行的效率,给网络用户一个良好的体验效果。在Microsoft提供的经典示例项目.Net PetShop 4.0中,也提供了对缓存的支持,本文是作者在学习此项目时的一些心得体会,有一些地方还不十分清楚,希望能够抛砖引玉。

在.Net PetShop 4.0中,非常成功地使用了工厂模式以及接口(interface)、静态类(Static class)、抽象类(abstract class)等成员。在使用缓存时,也是通过web.config配置进行设置,在使用时非常灵活。下面从底向上具体分析.Net PetShop 4.0缓存方面的技术。

首先看一下该项目中与缓存直接相关的命名空间:

PetShop.ICacheDependency
PetShop.TableCacheDependency
PetShop.CacheDependencyFactory
PetShop.Web

一、PetShop.ICacheDependency命名空间

最低层应该是接口的定义了,在PetShop.ICacheDependency命名空间中只定义了一个接口IPetShopCacheDependency,该接口只有一个方法 GetDependency,没有任何参数,返回AggregateCacheDependency类型。AggregateCacheDependency是在.NET Framework 2.0 版中是新增的类,组合 ASP.NET 应用程序的 Cache 对象中存储的项和 CacheDependency 对象的数组之间的多个依赖项(MSDN中原话)。

二、PetShop.TableCacheDependency命名空间

在PetShop.TableCacheDependency命名空间中,提供两种类:抽象类TableDependency和它的继承类Category、Item和Product。抽象类TableDependency的构造函数为:

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));
}

传递了一个参数configKey,根据该参数从web.config文件中获取表名列表,同时在web.config中获取数据库名称。将表名列表中的所有数据表添加到AggregateCacheDependency类型的dependency变量中。在此外使用了.NET Framework 2.0 版中是新增的另一个与缓存有关的SqlCacheDependency类。这个类用于建立ASP.NET应用程序的Cache对象中存储的项和特定SQL Server数据库表之间的联系。AggregateCacheDependency和SqlCacheDependency都从CacheDependency继承而来,但在.NET 2.0中还未提供Oracle等其它数据库对应的类。

下面是web.config文件中与缓存相关的设置:

<!--  Cache dependency options. Possible values: PetShop.TableCacheDependency for SQL Server and keep empty for ORACLE  -->
< add  key ="CacheDependencyAssembly"  value ="PetShop.TableCacheDependency" />
<!--  CacheDatabaseName should match the name under caching section, when using TableCacheDependency  -->
< add  key ="CacheDatabaseName"  value ="MSPetShop4" />
<!--  *TableDependency lists table dependency for each instance separated by comma  -->
< add  key ="CategoryTableDependency"  value ="Category" />
< add  key ="ProductTableDependency"  value ="Product,Category" />
< add  key ="ItemTableDependency"  value ="Product,Category,Item" />

每个继承类都只有一个构造函数,通过设置基类的configKey参数变成了三个不同的类。Product类的构造函数为:

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

三、PetShop.CacheDependencyFactory命名空间

在PetShop.CacheDependencyFactory命名空间中有两个类,分别是DependencyAccess和DependencyFacade。如果了解微软以前提供的一些经典例子的分层,便不难理解这两个类的命名以及它们对应的层次级别。这两个类都是静态类,DependencyAccess类里面的方法都是返回IPetShopCacheDependency,而DependencyFacade里面的方法都是返回AggregateCacheDependency。

DependencyAccess类里面关键的一个方法是LoadInstance,它是工厂模式的具体实现。其代码如下:

private   static  IPetShopCacheDependency LoadInstance( string  className) {

    
string  path  =  ConfigurationManager.AppSettings[ " CacheDependencyAssembly " ];
    
string  fullyQualifiedClass  =  path  +   " . "   +  className;

    
//  Using the evidence given in the config file load the appropriate assembly and class
     return  (IPetShopCacheDependency)Assembly.Load(path).CreateInstance(fullyQualifiedClass);
}

在这个方法中通过配置文件中的设置和传进来的参数className,返回相对应的程序集和类。DependencyAccess类里面的其它三个方法,只是调用这个方法,传入不同的参数而已。

DependencyFacade类提供的三个方法正好与DependencyAccess类的三个方法相对应,分别获取Category、Item和Product的AggregateCacheDependency。在DependencyFacade类中还读取了web.config中的CacheDependencyAssembly设置,从而决定是调用DependencyAccess对应的方法,还是直接返回null。

四、PetShop.Web命名空间

在PetShop.Web的App_Code中,有四个静态类与缓存直接相关,分别是CategoryDataProxy、ItemDataProxy、ProductDataProxy和WebUtility。其中前三个分别调用DependencyFacade对应的方法。

WebUtility中有两个方法GetCategoryName和GetProductName使用了缓存,下面是GetCategoryName的部分代码:

 1
 2          public   static   string  GetProductName( string  productId)  {
 3
 4            Product product = new Product();
 5            if (!enableCaching)
 6                return product.GetProduct(productId).Name;
 7
 8            string cacheKey = string.Format(PRODUCT_NAME_KEY, productId);
 9
10            // Check if the data exists in the data cache
11            string data = (string)HttpRuntime.Cache[cacheKey];
12
13            if (data == null{
14                // Caching duration from Web.config
15                int cacheDuration = int.Parse(ConfigurationManager.AppSettings["ProductCacheDuration"]);
16
17                // If the data is not in the cache then fetch the data from the business logic tier
18                data = product.GetProduct(productId).Name;
19
20                // Create a AggregateCacheDependency object from the factory
21                AggregateCacheDependency cd = DependencyFacade.GetProductDependency();
22
23                // Store the output in the data cache, and Add the necessary AggregateCacheDependency object
24                HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
25
26            }

27
28            return data;
29        }

这个方法中通过 productId 来得到 productName,enableCaching用来判断当前是否需要使用缓存,cacheKey 得到一个当前productName的标示,检查缓存里面如果没有对这个标示缓存过,然后就得到cacheDuration缓存时间,从数据库得到data即productName,之后得到此类的AggregateCacheDependency对象,最后加去Cache 缓存里面这就完成了一个缓存的动作。

缓存的机制大概是这样:通过Cache缓存读出的纪录,只要此纪录不被改变在Cache的时间有效期之类一直会读取缓存内容。如果Cache的数据发生了改变那么就会根据一个时间(这个时间在Web.Config配制文件的Cache里面设置,自动监视查数据库的是否更新)使得Cache实效来从数据库里面读取新的值来更新缓存。

在.Net 2.0中,对缓存的维护有两种方式:第一种是每次使用缓存之间进行判断缓存是否存,如果不存在则读取数据存入缓存;另外一种方式是使用CacheItemRemovedCallback委托来实现,当缓存失效时自动调用委托过程,重新产生缓存。从上面的代码来看,.Net PetShop 4.0使用的是第一种方式。

 其实仔细点的人会发现在整个PetShop的Solution里面没有使用Proxy,为什么呢。因为微软还用了另外一种缓存机制。页面缓存结合Dependency ,有兴趣的自己研究下,还不好说哪种缓存的比较好。

参考资料:

http://msdn2.microsoft.com/zh-cn/library/system.web.caching.sqlcachedependencyadmin.aspx

http://www.microsoft.com/china/msdn/library/webservices/asp.net/CachingNT2.mspx?mfr=true

你可能感兴趣的:(缓存)