许多做过程序性能优化的人,或者关注过程程序性能的人,应该都使用过各类缓存技术。而我今天所说的Cache是专指ASP.NET的Cache,我们可以使用HttpRuntime.Cache访问到的那个Cache,而不是其它的缓存技术。
以前我在【我心目中的Asp.net核心对象】这篇博客中简单地提过它,今天我打算为它写篇专题博客,专门来谈谈它,因为它实在是太重要了。在这篇博客中,我不仅要介绍它的一些常见用法,还将介绍它的一些高级用法。在上篇博客【在.net中读写config文件的各种方法】的结尾处,我给大家留了一个问题,今天,我将在这篇博客中给出一个我认为较为完美的答案。
本文提到的【延迟操作】方法(如:延迟合并写入数据库)属于我的经验总结,希望大家能喜欢这个思路。
Cache的基本用途
提到Cache,不得不说说它的主要功能:改善程序性能。
ASP.NET是一种动态页面技术,用ASP.NET技术做出来的网页几乎都是动态的,所谓动态是指:页面的内容会随着不同的用户或者持续更新的数据,而呈现出不同的显示结果。既然是动态的,那么这些动态的内容是从哪里来的呢?我想绝大多数网站都有自己的数据源,程序通过访问数据源获取页面所需的数据,然后根据一些业务规则的计算处理,最后变成适合页面展示的内容。
由于这种动态页面技术通常需要从数据源获取数据,并经过一些计算逻辑,最终变成一些HTML代码发给客户端显示。而这些计算过程显然也是有成本的。这些处理成本最直接可表现为影响服务器的响应速度,尤其是当数据的处理过程变得复杂以及访问量变大时,会变得比较明显。另一方面,有些数据并非时刻在发生变化,如果我们可以将一些变化不频繁的数据的最终计算结果(包括页面输出)缓存起来,就可以非常明显地提升程序的性能,缓存的最常见且最重要的用途就体现在这个方面。这也是为什么一说到性能优化时,一般都将缓存摆在第一位的原因。我今天要说到的ASP.NET Cache也是可以实现这种缓存的一种技术。不过,它还有其它的一些功能,有些是其它缓存技术所没有的。
Cache的定义
在介绍Cache的用法前,我们先来看一下Cache的定义:(说明:我忽略了一些意义不大的成员)- // 实现用于 Web 应用程序的缓存。无法继承此类。
- public sealed class Cache : IEnumerable
- {
- // 用于 Cache.Insert(...) 方法调用中的 absoluteExpiration 参数中以指示项从不过期。
- public static readonly DateTime NoAbsoluteExpiration;
- // 用作 Cache.Insert(...) 或 Cache.Add(...)
- // 方法调用中的 slidingExpiration 参数,以禁用可调过期。
- public static readonly TimeSpan NoSlidingExpiration;
- // 获取或设置指定键处的缓存项。
- public object this[string key] { get; set; }
- // 将指定项添加到 System.Web.Caching.Cache 对象,该对象具有依赖项、过期和优先级策略
- // 以及一个委托(可用于在从 Cache 移除插入项时通知应用程序)。
- public object Add(string key, object value, CacheDependency dependencies,
- DateTime absoluteExpiration, TimeSpan slidingExpiration,
- CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback);
- // 从 System.Web.Caching.Cache 对象检索指定项。
- // key: 要检索的缓存项的标识符。
- // 返回结果: 检索到的缓存项,未找到该键时为 null。
- public object Get(string key);
- public void Insert(string key, object value);
- public void Insert(string key, object value, CacheDependency dependencies);
- public void Insert(string key, object value, CacheDependency dependencies,
- DateTime absoluteExpiration, TimeSpan slidingExpiration);
- // 摘要:
- // 向 System.Web.Caching.Cache 对象中插入对象,后者具有依赖项、过期和优先级策略
- // 以及一个委托(可用于在从 Cache 移除插入项时通知应用程序)。
- //
- // 参数:
- // key:
- // 用于引用该对象的缓存键。
- //
- // value:
- // 要插入缓存中的对象。
- //
- // dependencies:
- // 该项的文件依赖项或缓存键依赖项。当任何依赖项更改时,该对象即无效,
- // 并从缓存中移除。如果没有依赖项,则此参数包含 null。
- //
- // absoluteExpiration:
- // 所插入对象将过期并被从缓存中移除的时间。
- // 如果使用绝对过期,则 slidingExpiration 参数必须为 Cache.NoSlidingExpiration。
- //
- // slidingExpiration:
- // 最后一次访问所插入对象时与该对象过期时之间的时间间隔。如果该值等效于 20 分钟,
- // 则对象在最后一次被访问 20 分钟之后将过期并被从缓存中移除。如果使用可调过期,则
- // absoluteExpiration 参数必须为 System.Web.Caching.Cache.NoAbsoluteExpiration。
- //
- // priority:
- // 该对象相对于缓存中存储的其他项的成本,由 System.Web.Caching.CacheItemPriority 枚举表示。
- // 该值由缓存在退出对象时使用;具有较低成本的对象在具有较高成本的对象之前被从缓存移除。
- //
- // onRemoveCallback:
- // 在从缓存中移除对象时将调用的委托(如果提供)。
- // 当从缓存中删除应用程序的对象时,可使用它来通知应用程序。
- //
- // 异常:
- // System.ArgumentException:
- // 为要添加到 Cache 中的项设置 absoluteExpiration 和 slidingExpiration 参数。
- //
- // System.ArgumentNullException:
- // key 或 value 参数为 null。
- //
- // System.ArgumentOutOfRangeException:
- // 将 slidingExpiration 参数设置为小于 TimeSpan.Zero 或大于一年的等效值。
- public void Insert(string key, object value, CacheDependency dependencies,
- DateTime absoluteExpiration, TimeSpan slidingExpiration,
- CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback);
- // 从应用程序的 System.Web.Caching.Cache 对象移除指定项。
- public object Remove(string key);
- // 将对象与依赖项策略、到期策略和优先级策略
- // 以及可用来在从缓存中移除项【之前】通知应用程序的委托一起插入到 Cache 对象中。
- // 注意:此方法受以下版本支持:3.5 SP1、3.0 SP1、2.0 SP1
- public void Insert(string key, object value, CacheDependency dependencies,
- DateTime absoluteExpiration, TimeSpan slidingExpiration,
- CacheItemUpdateCallback onUpdateCallback);
- }
复制代码 ASP.NET为了方便我们访问Cache,在HttpRuntime类中加了一个静态属性Cache,这样,我们就可以在任意地方使用Cache的功能。而且,ASP.NET还给它增加了二个“快捷方式”:Page.Cache, HttpContext.Cache,我们通过这二个对象也可以访问到HttpRuntime.Cache,注意:这三者是在访问同一个对象。Page.Cache访问了HttpContext.Cache,而HttpContext.Cache又直接访问HttpRuntime.Cache
Cache常见用法
通常,我们使用Cache时,一般只有二个操作:读,写。
要从Cache中获取一个缓存项,我们可以调用Cache.Get(key)方法,要将一个对象放入缓存,我们可以调用Add, Insert方法。然而,Add, Insert方法都有许多参数,有时我们或许只是想简单地放入缓存,一切接受默认值,那么还可以调用它的默认索引器,我们来看一下这个索引器是如何工作的:- public object this[string key]
- {
- get
- {
- return this.Get(key);
- }
- set
- {
- this.Insert(key, value);
- }
- }
复制代码 可以看到:读缓存,其实是在调用Get方法,而写缓存则是在调用Insert方法的最简单的那个重载版本。
注意了:Add方法也可以将一个对象放入缓存,这个方法有7个参数,而Insert也有一个签名类似的重载版本,它们有着类似的功能:将指定项添加到 System.Web.Caching.Cache 对象,该对象具有依赖项、过期和优先级策略以及一个委托(可用于在从 Cache 移除插入项时通知应用程序)。然而,它们有一点小的区别:当要加入的缓存项已经在Cache中存在时,Insert将会覆盖原有的缓存项目,而Add则不会修改原有缓存项。
也就是说:如果您希望某个缓存项目一旦放入缓存后,就不要再被修改,那么调用Add确实可以防止后来的修改操作。而调用Insert方法,则永远会覆盖已存在项(哪怕以前是调用Add加入的)。
从另一个角度看,Add的效果更像是 static readonly 的行为,而Insert的效果则像 static 的行为。
注意:我只是说【像】,事实上它们比一般的static成员有着更灵活的用法。
由于缓存项可以让我们随时访问,看起来确实有点static成员的味道,但它们有着更高级的特性,比如:缓存过期(绝对过期,滑动过期),缓存依赖(依赖文件,依赖其它缓存项),移除优先级,缓存移除前后的通知等等。后面我将会分别介绍这四大类特性。
Cache类的特点
Cache类有一个很难得的优点,用MSDN上的说话就是:
此类型是线程安全的。
为什么这是个难得的优点呢?因为在.net中,绝大多数类在实现时,都只是保证静态类型的方法是线程安全,而不考虑实例方法是线程安全。这也算是一条基本的.NET设计规范原则。
对于那些类型,MSDN通常会用这样的话来描述:
此类型的公共静态(在 Visual Basic 中为 Shared)成员是线程安全的。但不能保证任何实例成员是线程安全的。
所以,这就意味着我们可以在任何地方读写Cache都不用担心Cache的数据在多线程环境下的数据同步问题。多线程编程中,最复杂的问题就是数据的同步问题,而Cache已经为我们解决了这些问题。
不过我要提醒您:ASP.NET本身就是一个多线程的编程模型,所有的请求是由线程池的线程来处理的。通常,我们在多线程环境中为了解决数据同步问题,一般是采用锁来保证数据同步,自然地,ASP.NET也不例外,它为了解决数据的同步问题,内部也是采用了锁。
说到这里,或许有些人会想:既然只一个Cache的静态实例,那么这种锁会不会影响并发?
答案是肯定的,有锁肯定会在一定程度上影响并发,这是没有办法的事情。
然而,ASP.NET在实现Cache时,会根据CPU的个数创建多个缓存容器,尽量可能地减小冲突,以下就是Cache创建的核心过程:
[code]internal static CacheInternal Create(){ CacheInternal internal2; int numSingleCaches = 0; if( numSingleCaches == 0 ) { uint numProcessCPUs = (uint)SystemInfo.GetNumProcessCPUs(); numSingleCaches = 1; for( numProcessCPUs -= 1; numProcessCPUs > 0; numProcessCPUs = numProcessCPUs >> 1 ) { numSingleCaches = numSingleCaches > 1 ) { numSingleCaches = numSingleCaches |