代码改变世界

[资料收集]PetShop 缓存(asp.net 2.0)

2007-09-28 18:52  水随风  阅读(603)  评论(0)    收藏  举报

 

.NET Petshop详解(五):petshop输出缓存设置

ASP.NET的输出缓存

衡量高性能、可缩放的web应用程序最重要的一个指标就是缓存了。ASP.NET提供了高性能的web应用程序的缓存功能,ASP.NET 有三种可由 Web 应用程序使用的缓存:

· 输出缓存,它缓存请求所生成的动态响应。

· 片断缓存,它缓存请求所生成的响应的各部分。

· 数据缓存,它以编程方式缓存任意对象。为支持这种缓存,ASP.NET 提供了全功能的缓存引擎,使程序员能够轻松地在请求间保留数据。

页的输出缓存是非常有用的。在海量的访问站点中,有些页面的访问频率占了非常大的比重,即使对这些页使用输出缓存很少的时间,也会减轻系统不少的负担,因为后面对这些页面的请求将不在执行创建该页的代码。

但是,这样显得不够灵活,页的请求可能的确是很多,然而在页面上我们缓存了所有的东西,无论是构造成本高还是构造成本低的部分。能否有一种可以缓存页的部分的数据呢?幸运的是ASP.NET提供了针对每个请求来创建或自定义该页的各部分。比如说我们可以对页面上构造成本很高的用户控件做片断缓存。

ASP.NET 缓存支持文件和缓存键依赖项,使开发人员可以使缓存项依赖于外部文件或其他缓存项。此项技术可用于在项的基础数据源发生更改时使该项无效。 ASP.NET可以将这些项存储在 Web 服务器上或请求流中的其他软件上,例如代理服务器或浏览器。这可以使您避免重新创建满足先前请求的信息,特别是当在服务器上创建时要求大量处理器时间或其他资源的信息。

Petshop的页缓存设置

我们可以可通过使用低级别的 OutputCache API 或高级别的 @ OutputCache指令来实现页的输出缓存。启用输出缓存后,当发出对页的第一个 GET请求时创建一个输出缓存项。随后的 GETHEAD请求由该输出缓存项服务,直到该缓存请求过期。输出缓存还支持缓存的 GETPOST名称/值对的变体。

输出缓存遵循页的过期和有效性策略。如果某页位于输出缓存中,并且有一个过期策略标记指示该页自缓存起 60 分钟后过期,则在 60 分钟后将该页从输出缓存中移除。如果此后接收到另一个请求,则执行页代码,并且可以再次缓存该页。

下面的指令在响应时激活输出缓存:

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

Duration和VaryByParam是必选参数,前者标识过期时间,后者表示GETPOST名称/值对的字符串。如果不使用该属性,可是设置为none。在这里我们还要说明一个参数VaryByCustom,使用这个参数,我们可以自定义输出缓存要求的任意文本。除了在OutputCache指令里面申明该属性之外,我们还得在应用程序的 global.asax 文件的代码声明块中,重写 GetVaryByCustomString 方法来为自定义字符串指定输出缓存的行为。

举一列来说:

<%@ OutputCache VaryByParam="none" VaryByCustom="CategoryPageKey" Location="server" Duration="43200" %>

这里的VaryByCustom定义的为CategoryPageKey,那么在global.asax里面我们必须定义CategoryPageKey这个字符创输出缓存的行为,见下面代码。

public override string GetVaryByCustomString(HttpContext context, String arg) {

                 string cacheKey = "";

                 switch(arg) {

                      case "CategoryPageKey":

                            if (Request.IsAuthenticated == true) {

                                  cacheKey = "QQQ" + context.Request.QueryString["category_id"] + context.Request.QueryString["requestedPage"];

                            }

                            else {

                                  cacheKey = "AAA" + context.Request.QueryString["category_id"] + context.Request.QueryString["requestedPage"];

                            }

                            break;

                      case "SearchPageKey" :

                            if (Request.IsAuthenticated == true) {

                                  cacheKey = "QQQ" + context.Request.QueryString["search_text"] + context.Request.QueryString["requestedPage"];

                            }

                            else {

                                  cacheKey = "AAA" + context.Request.QueryString["search_text"] + context.Request.QueryString["requestedPage"];

                            }

                            break;

                      case "ProductPageKey" :

                            if (Request.IsAuthenticated == true) {

                                  cacheKey = "QQQ" + context.Request.QueryString["name"] + context.Request.QueryString["product_id"] + context.Request.QueryString["requestedPage"];

                            }

                            else {

                                       cacheKey = "AAA" + context.Request.QueryString["name"] + context.Request.QueryString["product_id"] + context.Request.QueryString["requestedPage"];

                            }

                            break;

                      case "ProductDetailsPageKey" :

                            if (Request.IsAuthenticated == true) {

                                  cacheKey = "QQQ" + context.Request.QueryString["item_id"] + context.Request.QueryString["requestedPage"];

                            }

                            else {

                                  cacheKey = "AAA" + context.Request.QueryString["item_id"] + context.Request.QueryString["requestedPage"];

                            }

                            break;

                      case "UserID" :

                            if (Request.IsAuthenticated == true) {

                                  cacheKey = "UserID_In";

                            }

                            else {

                                  cacheKey = "UserID_Out";

                            }

                            break;

                 }

                 return cacheKey;

           }

从上面对CategoryPageKey字符创所作的行为来看,当我们的请求页面中含有对特定的category_id的某一分页显示的数据页的请求时,将调用缓存(自然是已经缓存了该页)。

下表列出了petshop的web应用程序的输出缓存设置。

ASP.NET WebForms

Cache setting

Duration

ControlHeader

<%@ OutputCache

         Duration="43200 "         

         VaryByParam="none"

         VaryByCustom="UserID" %>

12 hours

Default

<%@ OutputCache

         Duration="43200"

         VaryByParam="none"

         VaryByCustom="UserID" %>

12 hours

Help

<%@ OutputCache

         Duration="43200" 

         VaryByParam="none"

         VaryByCustom="UserID" %>

12 hours

Category

<%@ OutputCache

         Duration="43200" 

         VaryByParam="none"

         VaryByCustom="CategoryPageKey " %>

12 hours

Product

<%@ OutputCache

         Duration="43200" 

         VaryByParam="none"

         VaryByCustom="ProductPageKey " %>

12 hours

ProductDetails

<%@ OutputCache

         Duration="43200" 

        VaryByParam="none"

         VaryByCustom="ProductDetailsPageKey " %>

12 hours

Search

<%@ OutputCache

         Duration="43200" 

         VaryByParam="none"

         VaryByCustom="SearchPageKey " %>

12 hours

显然petshop的web页面上部的ControlHeader是随着用户登陆的状态有关的,故其设置了VaryByCustom属性以来标识用户不同登陆状态的缓存版本。而Category页面由于可能被大量的访问,并且数据量很大,是十分有必要缓存的,但是由于数据的随机性很大,存在不同的版本,比如说是不同类别的Category,甚至不同的分页显示的数据页,在这里采用了VaryByCustom属性以缓存不同版本的页。

Petshop片断缓存

在前面我们提到ASP.NET可以提供页的局部数据的缓存,通常是一些构造代价较大的部分,诸如用户控件。在petshop里面,大量使用了用户控件(尤其是.NET示例程序Duwamish7.0使用了更多的用户控件,那些页面简直就是控件的拼装),用户控件的缓存设置方法和aspx页的缓存设置方法基本相同,在这里我们不再列出。只有ControlHeader控件使用了缓存设置,见上表

周一在新员工培训上为他们做了一个PetShop 4.0的talk,(感谢Zou老师给我这么好的一个机会,让我自今还觉得相当incredible,不过也证明了MSRA里面,Imporsible is nothing)

结果这两天不停有人跑来问我各种各样的Pet Shop问题,还好凭着自己的三脚猫功夫,能够把大部分人心满意足地打发走。但是昨天就碰上了一个问题,让我一开始也是百思不得其解。那就是Pet Shop中的缓存机制。

首先,Pet Shop中应用了ASP.Net 2.0中新增加的SqlCacheDependency能力,通过配置文件的设置,Framwork会自动定时轮询数据库中的一个监视表,而被监视表上有一个触发器,在发生Insert、Update、Delete操作的时候就会改变监视表中ChangeID的值。这样一来,每当ChangeID发生了变化,SqlCacheDependency就会自动使得Cache失效,那么下一次请求到达服务器时,程序将直接从数据库中提取出最新的数据并重新插入 Cache中,于是Cache的过期就不必手动编码操作了!

但是仔细查看代码会发现,Web项目的App_Code文件夹下有一系列的DataProxy类,里面都是静态方法,通过代码不难看出,这些类是用于数据访问的,即首先判断是否所需数据已经存在于缓存中,若有,则直接访问缓存,若无,则访问数据库返回填充好的数据实体对象并缓存起来,插入缓存时,正使用了SqlCacheDependency机制。

可是如果你在整个Solution里面搜索相应的DataProxy类,你会一无所获!难道这些代码只是说放在那里而已,难道它们也只是为了显示一下自己的新锤子?

一开始我也百思不得其解,后来看到页面里面的代码时,猛地恍然大悟。原来Pet Shop中实现了两套缓存方式,一种是类似于DataProxy的那种传统方法,将数据实体缓存起来,另一种则是结合了ASP.NET的页面缓存机制来实现的。

查看ProductsControl.ascx文件你会在头部发现一个伪指令声明:<%@ OutputCache Duration="100000" VaryByParam="page;categoryId" %>第一个参数是定义了控件的过期时间,第二个参数则定义了区分缓存的参数(2.0新特性),然后查看 ProductsControl.ascx.cs文件你会发现在protected void PageChanged方法中有productsList.DataSource = product.GetProductsByCategory(categoryKey);语句,这个语句是直接使用DAL访问数据库,(没有先查询缓存,这也是很多人百思不得其解的地方)但是关键不在这里,在protected void Page_Load方法中有一行语句this.CachePolicy.Dependency = DependencyFacade.GetProductDependency();这条语句的作用就在于将SqlCacheDependency同样应用于页面的缓存过期。

这样一来,缓存的就不光是数据实体类而已,而是整个控件被Render出来后的html!这样一来还省去了每次访问页面时的Render操作,效率显然比光缓存实体类高得多!

ps: 如果你想使用光缓存实体类的方式,那么请删除ascx文件中的OutputCache声明,注释掉page_load中的过期策略语句,然后将 productsList.DataSource = product.GetProductsByCategory(categoryKey);改为productsList.DataSource = ProductDataProxy.GetProductsByCategory(categoryKey);其他页面同理操作。

结论是PetShop实现了两种缓存方式,但是默认它使用了更高效的那种。可惜又让初学者看不懂了,22个项目,残念ing...

【简 介】在访问量非常大,但更新较少的网站中使用缓存,可以大大提高程序运行的效率,给网络用户一个良好的体验效果。

在访问量非常大,但更新较少的网站中使用缓存,可以大大提高程序运行的效率,给网络用户一个良好的体验效果。在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文件中与缓存相关的设置:

  








  每个继承类都只有一个构造函数,通过设置基类的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的部分代码:

if (data == null) {
// Caching duration from Web.config
int cacheDuration = int.Parse(ConfigurationManager.AppSettings["CategoryCacheDuration"]);

// If the data is not in the cache then fetch the data from the business logic tier
data = category.GetCategory(categoryId).Name;

// Create a AggregateCacheDependency object from the factory
AggregateCacheDependency cd = DependencyFacade.GetCategoryDependency();

// Store the output in the data cache, and Add the necessary AggregateCacheDependency object
HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
}
  在.Net 2.0中,对缓存的维护有两种方式:第一种是每次使用缓存之间进行判断缓存是否存,如果不存在则读取数据存入缓存;另外一种方式是使用CacheItemRemovedCallback委托来实现,当缓存失效时自动调用委托过程,重新产生缓存。从上面的代码来看,.Net PetShop 4.0使用的是第一种方式。