介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题
1 概念
1.1 缓存能解决的问题
· 性能——将相应数据存储起来以避免数据的重复创建、处理和传输,可有效提高性能。比如将不改变的数据缓存起来,例如国家列表等,这样能明显提高web程序的反应速度;
· 稳定性——同一个应用中,对同一数据、逻辑功能和用户界面的多次请求时经常发生的。当用户基数很大时,如果每次请求都进行处理,消耗的资源是很大的浪费,也同时造成系统的不稳定。例如,web应用中,对一些静态页面的呈现内容进行缓存能有效的节省资源,提高稳定性。而缓存数据也能降低对数据库的访问次数,降低数据库的负担和提高数据库的服务能力;
· 可用性——有时,提供数据信息的服务可能会意外停止,如果使用了缓存技术,可以在一定时间内仍正常提供对最终用户的支持,提高了系统的可用性。
1.2 理解状态
在深入介绍缓存技术之前,需要对状态有一个认识,因为缓存可以说是状态管理的框架。理解状态的含义和它的一些特性——比如生存期和生存范围——对决定是否缓存和选择合适的缓存技术有很大帮助。状态是指一些数据,在应用系统某个时间点上,数据的状态和条件。这些数据可能是永久性的存储在数据库中,可能是只在内存里停留一会,也可能是按照某个逻辑存活(比如多长时间后释放),它的应用范围可能是所有用户可访问,可能是单个用户有权限;
1.2.1 状态的生存期
生存期是指数据保持有效性的时间区间,也就是从创建到移除的时间间隔。通常的生存期有以下几种:
·永久状态Permanent State——应用程序使用的永久数据;
·进程状态Process State——只在进程周期内有效;
·会话状态Session State——和特定的用户会话有关;
·消息状态Message State——处理某个消息的时间内有效;
1.2.2 状态的范围
状态的范围指对该状态有访问权限的物理或逻辑范围。
·物理范围指可以被访问到的状态数据存放的物理位置,通常包括:
1、 组织Organization——在一个组织内的所有应用程序可以访问状态数据;
2、 场Farm——在应用场范围内的任何机器上都可以访问;
3、 机器Machine——单个机器范围内可以访问;
4、 进程Process——进程内的访问许可;
5、 应用域AppDomain——应用程序域内的访问许可。
·逻辑范围指可访问状态数据的逻辑范围,常见的有:
1、 应用程序Application;
2、 业务流程Business Process;
3、 角色Role;
4、 用户User;
1.2.3 状态数据的陈旧
缓存的状态数据只是主数据(Master State Data)的快照,由于数据源可能被修改,所以状态数据就有会陈旧的特性。合理利用此特性和将数据陈旧的负面影响最小化是缓存状态数据的一个重要任务。你可以以一下方式定义数据的陈旧依据:
·主数据更改的可能性——随着时间的推进,主数据更改的可能是否大大增加?安照这一点来决定缓存状态数据的陈旧;
·更改的相关性——主数据更新时,缓存的状态数据不相应更新是不是造成影响系统的使用?比如,更改系统的外观风格并不会对业务造成很大影响。
1.2.4 状态数据陈旧的容忍度
缓存状态数据的陈旧对业务流程的影响称为容忍度,应用系统的可以为不能容忍(No Tolerance)和一定程度的容忍(some Tolerance),前者必须和主数据同步更新,后者允许一定时间或一定范围的陈旧,判断标准就是对业务流程的影响度。
1.2.5 理解状态数据的转换过程
状态的另一个属性是在不同阶段的表现形式。在数据库中存储的是原始格式的数据,业务流程中的是处理过的数据,给最终用户呈现的则是另外的形式。如下表所示:
表现形式
描述
举例
原始数据
数据的原始形式
如数据库中的数据
处理过的数据
业务流程中对原始数据加工后的数据
业务过程中的数据形式
呈现形式
可呈现给最终用户的形式
HTML或可理解的文字说明
当决定缓存数据时,应该考虑缓存哪个阶段(哪种形式)的状态数据。以下方针有助于你做决定:
· 当业务逻辑可以容忍缓存数据的陈旧时就缓存原始数据;原始数据可以缓存在数据库访问组件和服务代理中;
·缓存处理过的数据以减少处理时间和资源;处理过的数据可以缓存在业务逻辑组件和服务接口中。
·当需要呈现的数据量很大并且控件的呈现时间很长时,缓存呈现数据(比如包含大数据量的Treeview控件)。这种数据应该被缓存在UI控件中。
1.3 为什么要缓存数据
在应用程序中缓存数据有以下好处:
·减少交互的通讯量——缓存数据能有效减少在进程和机器间的传输量;
·降低系统中的处理量——减少处理次数;
·降低需要做的磁盘访问次数——比如缓存在内存中的数据。
1.4 数据应该被缓存在哪里
缓存数据只是一份主数据的拷贝,它可能在内存中或以不同的表现形式保存在硬盘上,也就是说,离说句的使用者越近越好。所以,除了考虑要缓存哪些数据以外,数据缓存在哪里也是一个主要的考量点。这个问题分为以下两个范围:
1、 存储类型Storage Type——数据可用的物理存储位置;
2、 层间的架构元素(Layered architecture elements)——数据可用的逻辑存储位置。
1.4.1 存储类型
缓存有很多实现方法,所有这些可以被分为两类,基于内存的缓存和基于磁盘的缓存:
1、 内存驻留缓存——包含在内存中临时存储数据的所有实现方法,通常在以下情况下使用:
a) 应用程序频繁使用同样的数据;
b) 应用程序需要经常获取数据;
通过将数据保留在内存中,你可以有效降低昂贵的磁盘访问操作,也可以通过将数据保留在使用者进程中来最大程度的减少跨进程的数据传输。
2、 磁盘驻留缓存——这种技术包含所有使用磁盘作为存储介质的缓存技术,如文件和数据库。在以下情况下基于磁盘的缓存是很有效的:
a) 处理大数据量时;
b) 应用服务提供的数据可能并不是总能使用(比如离线的情况);
c) 缓存的数据必须能在进程回收和机器重启的情况下保持有效;
通过缓存处理过的数据,你可以有效降低数据处理的负担,同时可减少数据交互的代价。
1.4.2 架构间元素
应用程序中的每个逻辑层的组件都会处理数据,下图标识了一些通用组件:
当使用这些组件进行工作时,你需要考虑哪些数据可以被缓存起来,还有以哪种方式进行缓存会对程序的整体性能和可用性有帮助,以上的这些元素都可以缓存相应的数据。当然,要考虑的远不止这些。
1.5 实施缓存时的考虑
当设计一个缓存方案时,不但要考虑缓存哪些数据、数据缓存到哪里,还有其它的因素需要考虑。
1.5.1 格式和访问模式
当决定是否缓存一个对象时,关于数据的格式和访问机制,你需要考虑三个主要问题:
1、 线程安全——当缓存的内容可以被多个线程访问时,使用某种锁定机制来保证数据不会被两个线程同时操作;
2、 序列化——将一个对象缓存时,需要将它序列化以便保存,所以包缓存的对象必须支持序列化;
3、 规格化缓存数据——缓存数据时,相对于要使用的数据格式而言,要保证数据的格式是优化过的。
1.5.2 内容加载
在使用缓存数据前,必须将数据加载到缓存中,有两种机制来加载数据:
·提前加载Proactive Load——使用这种方式时,你提前将所有的状态数据加载到缓存中,可能在应用程序或线程启动时进行,然后在应用程序或线程的生存期内一直缓存;
·动态加载Reactive Load——或称反应式加载,当使用这种方法时,在应用程序请求数据时取到数据,并且将它缓存起来以备后续使用。
1.5.3 过期策略
另外一个关键因素是如何保持缓存数据和主数据(文件或数据库或其他的应用程序资源)的一致性,你可以定义过期策略来决定缓存中的内容,如已经缓存的时间或者收到其他资源的通知。
1.5.4 安全性
当缓存数据时,需要非常清楚缓存中数据的潜在安全威胁。缓存中的数据可能会被别的进程访问或修改,而此进程对主数据是没有权限的。原因是当数据存储在原始位置时,有相应的安全机制来保护它,当数据被带出传统的安全边界时,需要有同等的安全机制。
1.5.5 管理
当你缓存数据时,应用系统需要的维护工作加大了。在发布应用程序时,需要配置相应的属性,比如缓存的大小限制和清除策略。同时要使用某种机制来监控缓存的效率(比如事件日志和性能计数器)
1.6 小结
第一节内容简单介绍了缓存技术中的概念、缓存数据的原因和方案、优势、实施缓存方案时的考虑等基本内容。现在你对缓存中涉及的内容有了一个大致了解,下面着重介绍可用的缓存技术。
2 缓存技术
本节将介绍以下技术:
使用Asp.Net缓存;
使用Remoting Singleton缓存;
使用内存映射文件;
使用SQL Server缓存;
使用静态变量缓存;
使用Asp.net 会话状态(Session State);
使用Asp.net客户端缓存和状态;
使用Internet Explorer缓存。
2.1 Asp.net缓存
将常用的数据保存在内存中对asp的开发人员来说并不陌生,Session对象和Application对象提供键值对来缓存数据,Session对象保存和单个用户有关的数据,Application对象可保留和应用程序有关的数据,每个用户都可以访问。
在Asp.net中,提供了专门用于缓存数据的Cache对象,它的应用范围是应用程序域。生存期是和应用程序紧密相关的,每当应用程序启动的时候就重新创建Cache对象。它域Application对象的主要区别就是提供了专门用于缓存管理的特性,比如依赖和过期策略。
你可以使用Cache对象和它的属性来实现高级的缓存功能,同时可以利用Asp.net Cache来对客户端输出的响应内容进行缓存。关于Asp.net中的缓存技术,有以下内容要介绍:
2.1.1 编程缓存Programmatic Caching
Cache对象定义在System.Web.Caching命名空间,可以使用HttpContext类的Cache属性或Page对象的Cache属性来得到Cache的引用,Cache对象除了存储键值对以外,还可以存储.net框架的对象。下面介绍相应的依赖和过期策略。
2.1.1.1 依赖和过期策略
当向缓存中加数据时,可以指定它的依赖关系来实现在某些情况下强制移除它。可用的方案包括以下几种:
·文件依赖(File Dependency)——当硬盘上的某个(某些)文件更改时,强制移除缓存数据;如:
CacheDependency cDependency = new
CacheDependency(Server.MapPath("authors.xml"));
Cache.Insert("CachedItem", item, cDependency);
·键值依赖(Key Dependency)——指定缓存中的某个数据项更改时移除。如:
// Create a cache entry.
Cache["key1"] = "Value 1";
// Make key2 dependent on key1.
String[] dependencyKey = new String[1];
dependencyKey[0] = "key1";
CacheDependency dependency = new CacheDependency(null, dependencyKey);
Cache.Insert("key2", "Value 2", dependency);
·基于时间的过期策略——按照预先定义的时间策略来使数据失效,可以是绝对时间(如某个日期的18:00)也可以是相对现在的相对时间。如:
/// Absolute expiration
Cache.Insert("CachedItem", item, null, DateTime.Now.AddSeconds(5),Cache.NoSlidingExpiration);
/// Sliding expiration
Cache.Insert("CachedItem", item, null, Cache.NoAbsoluteExpiration,
TimeSpan.FromSeconds(5));
使用太短和太长的过期时间都不行,不是造成用不上的缓存数据,就是缓存了陈旧的数据并加重了缓存负担,所以可以使用高并发的测试来决定过期时间的最佳值。
·另外有个问题就是如何实现对数据库的依赖,这就要求实现自己的通知机制,当数据库数据改变时能够通知你的缓存数据改变。可参考http://www.gotdotnet.com/team/rhoward的示例。
由于数据会过期,所以当使用缓存中的数据时,必须检查数据的有效性。如以下代码:
string data = (string)Cache["MyItem"];
if (data == null)
{
data = GetData();
Cache.Insert("MyItem", data);
}
DoSomeThingWithData(data);
依赖和过期策略指定了缓存中数据的移除方式,有时候你可能需要在移除发生时做一些工作,这能靠写代码来实现这一点,这就是我们要讲到的。
2.1.1.2 使用缓存回调(Cache Callback)
你可以定义回调,这样当移除自动发生时, 你可以不移除它或者使用新的数据来替换它。如:
CacheItemRemovedCallback onRemove = new CacheItemRemovedCallback(this.RemovedCallback);
Cache.Insert("CachedItem",
item,
null,
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
onRemove);
// Implement the function to handle the expiration of the cache.
public void RemovedCallback(string key, object value, CacheItemRemovedReason r)
{
// Test whether the item is expired, and reinsert it into the cache.
if (r == CacheItemRemovedReason.Expired)
{
// Reinsert it into the cache again.
CacheItemRemovedCallback onRemove = null;
onRemove = new CacheItemRemovedCallback(this.RemovedCallback);
Cache.Insert(key,
value,
null,
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
onRemove);
}
}
2.1.1.3 对缓存项使用优先级
当运行应用程序的服务器内存不足时,会自动清除缓存中的数据,称为“清除scavenging”。此时,Cache对象根据缓存项的优先级来决定先移除哪些缓存数据,你可以在代码中指定缓存项的优先级。参看MSDN中“CacheItemPriority 枚举”,如:
Cache.Insert("DSN", connectionString, null, d, t, CacheItemPriority.High, onRemove);
2.1.1.4 刷新数据(清除缓存)
没有直接的方法来刷新Asp.net的输出缓存,但是有替代方法(设置所有数据失效),比如:
Response.Cache.SetExpires(DateTime.Now)
这可以清除缓存,但页面上并不立刻体现出来,直到最初的缓存期结束,比如:
<%@ OutputCache Duration="10" VaryByParam="none" %>指令指定的缓存只会在10秒后才清除。通常并不需要清除所有缓存项,你只要重新加载数据更新缓存就够了。
2.1.2 输出缓存(Output Cache)
你可以使用两种方式的输出缓存来缓存需要传输和显示到客户端浏览器上的数据——页面输出缓存(Page Output Cache)和页面片断缓存(Page Fragment Cache)。当整个页面相对变化较少时,可以缓存整个页面;如果只是页面的一部分经常变化,可以使用片断缓存。
2.1.2.1 页面输出缓存
Page Output Caching将对页面请求的响应放入缓存中,后续对此页面的请求将直接从缓存中得到信息而不是重建此页面。可以通过添加Page指令(高级别,声明实现)来实现,也可以使用HTTPCachePolicy类来实现(低级别,程序实现)。本指南不打算介绍技术细节,只给出如何更好使用的指南和最佳实践。有四方面的内容:
1、 决定缓存的内容
页面输出缓存可缓存各种信息,缓存这些信息意味着你不需要经常处理同样的数据和结果,包括:
·经常被请求但不不改变的静态页面;
·更新频率和时间已知的页面(如显示股票价格的页面);
·根据HTTP参数,有几个可能输出的页面(如根据城市的代号显示该城市天气情况的页面);
·从Web Service返回的结果;如:
[WebMethod(CacheDuration=60)]
public string HelloWorld()
{
return "Hello World";
}
2、 缓存动态页面
基于输入参数、语言和浏览器类型改变的动态网页经常用到。你可以使用OutputCache的以下属性来实现对动态页面的缓存:
VaryByParam——基于输入参数不同缓存同一页面的多个版本;
VaryByHeader——基于Page Header的内容不同缓存页面的多个版本;
VaryByCustom——通过声明属性和重载GetVaryByCustomString方法来定制缓存处理页面的多个版本;
VaryByControl——基于控件中asp对象属性的不同来缓存控件。
对多个版本页面的缓存会降低可用内存,所以要仔细衡量缓存策略。s
3、 控制缓存的位置
你可以使用@OutputCache指令的OutputCacheLocation属性的枚举值来指定缓存的位置,如:
<%@ outputcache duration="10" varybyparam="none" Location="Server" %>
4、 配置页面输出缓存
有两种方式控制,你可以使用Page指令,也可以使用Cache API编程实现。参考以下两段代码:
//代码1,使用指令
<%@ OutputCache Duration="20" Location="Server" VaryByParam="state" VaryByCustom="minorversion" VaryByHeader="Accept-Language"%>
//代码2,编程实现
private void Page_Load(object sender, System.EventArgs e)
{
// Enable page output caching.
Response.Cache.SetCacheability(HttpCacheability.Server);
// Set the Duration parameter to 20 seconds.
Response.Cache.SetExpires(System.DateTime.Now.AddSeconds(20));
// Set the Header parameter.
Response.Cache.VaryByHeaders["Accept-Language"] = true;
// Set the cached parameter to 'state'.
Response.Cache.VaryByParams["state"] = true;
// Set the custom parameter to 'minorversion'.
Response.Cache.SetVaryByCustom("minorversion");
…
}
2.1.2.2 页面片断缓存
有时候缓存整个页面并不灵活,同时内存的发但也比较大,这时候应考虑片断缓存。页面片断缓存适合以下类型的数据:
·创建开销很大的页面片断(控件);
·包含静态数据的页面片断;
·可被多个用户使用的页面片断;
·多个页面共享的页面片断(如公用菜单条)
以下是缓存部分页面的例子:
// Partial caching for 120 seconds
[System.Web.UI.PartialCaching(120)]
public class WebUserControl : System.Web.UI.UserControl
{
// Your Web control code
}
2.1.3 在非Web项目中使用Asp.net缓存
Asp.net Cache位于System.Web命名空间,但由于它是一个通用的方案,所以仍然可以在引用此命名空间的任何非Web项目中使用它。
System.Web.Caching.Cache 类是对象的缓存,它可以通过System.Web.HttpRuntime.Cache 的静态属性或System.Web.UI.Page 和System.Web.HttpContext.Cache来访问。因此在请求上下文之外也可以存在,在每个应用程序域中只有一个实例,所以HttpRuntime.Cache对象可以在Aspnet_wp.exe之外的每个应用程序域中存在。以下代码演示了在普通应用里访问Cache对象:
HttpRuntime httpRT = new HttpRuntime();
Cache cache = HttpRuntime.Cache;
2.2 使用Remoting Singleton缓存
.Net Remoting提供了跨应用程序域、跨进程、跨计算机的程序运行框架。服务器激活的对象有两种激活模式,其中Singleton 类型任何时候都不会同时具有多个实例。如果存在实例,所有客户端请求都由该实例提供服务。如果不存在实例,服务器将创建一个实例,而所有后继的客户端请求都将由该实例来提供服务。由于 Singleton 类型具有关联的默认生存期,即使任何时候都不会有一个以上的可用实例,客户端也不会总接收到对可远程处理的类的同一实例的引用。所以将数据缓存起来可以在多个客户端之间共享状态信息。
为了使用.Net Remoting实现缓存方案,要保证远程对象的租约不过期,并且远程对象没有被垃圾回收器销毁(对象租约是指在系统删除该对象前它在内存中的生存期)。当实现缓存时,重载MarshalByRefObject的InitializeLifetimeService方法并且返回null,这样就能保证租约永远不过期并且相关的对象生存期是无限的。以下代码是一个示例:
public class DatasetStore : MarshalByRefObject
{
// A hash table-based data store
private Hashtable htStore = new Hashtable();
//Returns a null lifetime manager so that GC won't collect the object
public override object InitializeLifetimeService() { return null; }
// Your custom cache interface
}
注意:由于这种方案的成本较高、性能上的限制并且可能造成系统不稳定,通常采用基于Sql Server的方案来替代。
2.3 使用内存映射文件(Memory-Mapped File)
内存映射文件提供独一无二的特性,允许应用程序通过指针来访问磁盘上的文件——与访问动态内存趣的方式一样。所以你可以将应用程序进程中的某个地址段的数据映射到文件中,供多个跨应用程序域或跨进程访问。
在windows中,代码和数据是以以种方式处理的,表现形式都是内存页,而在内存页背后都是磁盘上的文件。唯一的不同磁盘上的文件类型不同。代码后面是可执行的镜像,而数据后面则是系统的页面文件。当多个应用程序共享内存时,系统的性能会有明显提升。
你可以使用内存映射文件的这种特性来实现同一台机器上的跨进程和跨应用程序域的缓存解决方案。基于内存映射文件的缓存方案包含以下组件:
·windows NT服务——启动时创建内存映射文件,停止时删除它。功能是向使用缓存的进程提供句柄。当然,也可以使用命名的内存映射文件来提供操作接口;
·缓存托管组件(Cache Management Dll)——实现特定的缓存功能,比如:
a. 插入和删除数据项到缓存中;
b. 使用算法清除缓存,比如最后使用算法(Least Recently Used);
c. 保证数据不被篡改;
基于内存映射文件的缓存方案可以用在应用程序的每个层中,但由于使用win32 API调用,所以并不容易实现。.Net 框架不支持内存映射文件,所以只能以非托管代码的方式运行,当然也不能利用.Net框架的有力特性,比如垃圾回收等。同时缓存数据项的管理功能需要定制开发,还要开发性能计数器来监控缓存的效果。
2.4 使用SQL Server缓存
如果需要在进程回收(重启)、机器重启或电源故障的过程中保持缓存数据的有效,基于内存的方案并不能满足要求。你可以使用基于永久数据存储的方案,如SQL server数据库或NTFS文件系统。
SQL Server在使用sql语句或存储过程得到数据时,对varchar和varBinary类型的数据有8k的大小限制,你必须使用.Net 框架提供的Ado.Net SQLDataAdapter对象来访问datatable或dataset。
使用SQL Server缓存数据的优点:
·易于实现——使用.Net 框架和Ado.Net访问数据库相当方便;
·完善的安全模型和很高的健壮性;
·数据非常方便的共享;
·数据的持久保留。
·支持很大的数据量。
·方便的管理工具
当然,也有缺点:
·需要安装SQL Server,对小型应用来说不合适;
·重新构造数据的性能和读取数据库的性能比较;
·网络负担。
2.5 使用静态变量缓存
静态变量常用来记录类的状态,你可以用它来创建定制的缓存对象。在定制的缓存类中将你的数据存储器声明为静态变量,并且提供维护接口(插入、删除和访问等)。如果没有特殊的缓存需求(比如依赖、失效策略等),使用静态变量缓存数据是很方便的。由于是在内存中,这种方案可提供对缓存数据的直接、高速的访问,当没有替代方案解决键值对的存储且对速度要求很高时,可以使用静态变量。当然,在asp.net中,应该使用Cache对象。
你可以使用这种方案保存大数据的对象,前提是它不经常更改。由于没有清除机制,大数据的内存消耗会影响性能。
你需要保证定制线程安全机制,或者使用.Net框架提供的同步对象,比如Hashtable。以下代码是使用Hashtable实现的例子:
static Hashtable mCacheData = new Hashtable();
应用范围:本方案的应用范围可以限制到类、模块或整个项目。如果变量定义为public,整个项目中的代码都能访问它,范围是整个应用程序域,实现了高效的共享。而它的生存期是和范围紧密相关的。
2.6 使用asp.net session state
你可以使用基于HttpSessionState对象的asp.net session state来缓存单个用户的会话状态信息。它解决了asp中会话状态的很多限制,包括:
·asp session要求客户端接受cookies,否则就不能使用session;而asp.net可以配置为不使用cookie;
·对web server场的情况,asp的session不能支持;当稳定性和可用性要求很高时,asp.net session state虽然效果不好,但对比较小的单个值scalar Value(比如登录信息),还是很有效。
Asp.net session有很大改进,下面描述使用范围和使用方式。
Asp.net session state有三种操作模式:
1、 进程内模式InProc——Session State信息在asp.net工作进程aspnet_wp.exe的进程的内存中存储。这是默认选项,这种情况下,如果进程或应用程序域被回收,则Session 状态信息也被回收;
2、 进程外模式State Server——状态信息序列化后保存在独立的状态进程中(AspNet_State.exe),所以状态信息可以保存在专门的服务器上(一个状态服务器State Server);
3、 Sql server模式——状态信息序列化后保存在SQL Server数据库中。
你可以通过调整配置文件中<sessionState>标签的mode属性来设置要使用的状态模式,比如使用SQL Server模式来在Web server场中共享状态信息。当然,这个优势也有缺点,就是状态信息需要序列化和反序列化,同时多了对数据库的写入和读取,所以性能上有开销,这是要仔细评估的。
2.6.1 选择使用模式
2.6.1.1 使用InProc模式
当使用进程内模式时,状态信息保存在aspnet_wp.exe的进程中。由于在web场的情况下aspnet_wp.exe的多个实例在同一台服务器上运行,所以进程内模式不适用与web场的情况。
进程内模式是唯一支持Session_End事件的session模式,当用户会话超时或中止时,可以运行Session_End中的事件处理代码来清除资源。
2.6.1.2 使用StateServer模式
StateServer模式使用指定的进程储存状态信息。因为它也是一种进程外模式,所以要保证你存储的对象是可序列化的,以支持跨进程传输。
当使用Session对象在web场的情况下使用时,必须保证web.config文件中的<MachineKey>元素在所有服务器上是唯一的。这样所有的服务器使用同样的加密方式,才能访问缓存中的数据。参考msdn中的“MachineKey元素”。
2.6.1.3 使用SQL Server模式
SQL Server模式下,当你使用信任连接(trusted_connection=true 或 integrated security=sspi)访问Session state信息时,不能在asp.net中使用身份用户模拟。
默认情况下,SQL Server将状态信息存储在TempDb数据库中,它在每次Sql server服务启动时会自动重新创建,当然,你可以指定自己的数据库以便在数据库重启的过程中也能保持数据。
2.6.2 决定使用Session对象要存储的内容
你可以使用Session对象缓存任何类型的.net框架数据,但是要了解对某种类型来说最好的方式是什么。有以下几点需要说明:
1、 对基本类型(比如Int,Byte,String)来说,可以使用任何方式。因为在选用进程外方式时,asp.net使用一个优化的内部方法来序列化和反序列化基本类型的数据;
2、 对复杂类型(如ArrayList)来说,只选用进程内方式。因为asp.net使用BinaryFormatter来序列化和反序列化这类数据,而这会影响性能的。当然,只有在State Server和SQL Server的方式下,才会进行序列化操作;
3、 缓存的安全问题,当在缓存中存储敏感数据时,需要考虑安全性,其它页面可以访问到缓存中的数据;
4、 避免缓存大数据,那会降低性能;
5、 这种缓存方式不支持过期策略、清除和依赖。
2.6.3 实现Session State
Asp.net提供了简单接口来操作Session State,并可使用Web.Config进行简单设置,当配置文件中的设置改变时,能够在页面上立刻体现出来,而不需要重新启动asp.net进程。
以下代码演示了使用SQL Server来实现Session数据的存储和使用。
<sessionState
mode="SQLServer"
stateConnectionString="tcpip=127.0.0.1:42424"
sqlConnectionString="data source=127.0.0.1; Integrated Security=SSPI"
cookieless="false"
timeout="20"
/>
private void SaveSession(string CartID)
{
Session["ShoppingCartID"] = CartID;
}
private void CheckOut()
{
string CartID = (string)Session["ShoppingCartID"];
if(CartID != null)
{
// Transfer execution to payment page.
Server.Transfer("Payment.aspx");
}
else
{
// Display error message.
}
}
2.7 使用Asp.net客户端缓存和状态
你还可以使用客户端存储页面信息的方式来降低服务器的负担,这种方法提供最低的安全保障,但却有最快的性能表现。由于需要将数据发送到客户端存储,所以数据量有限。
实现客户端缓存的机制有以下五种,接下来将依次介绍:
·隐藏栏位(Hidden Field)
·View State
·隐藏帧(Hidden Frame)
·Cookies
·Query String
这五种方式分别适合于存储不同类型的数据。
2.7.1 使用Hidden Field
你可以将经常改变的少量数据保存在HtmlInputHidden中来维护页面的状态。当每次页面回送的过程中,这些数据都会包含在表单中大送到服务器,所以你要使用HTTP POST方式来提交页面。
使用这种方式的优点如下:
不需要服务器资源,直接从页面中读取;
几乎所有的浏览器都支持;
实现简单;
由于数据在页面中,所以在web Farm的情况下也可使用。
缺点:
由于可以通过查看源码看到,所以可能会被篡改;
不支持复杂格式的数据,复杂数据必须使用解析字符串的方式来间接得到;
当存储大数据的时候会影响性能。
示例:
<input id="HiddenValue" type="hidden" value="Initial Value" runat="server" NAME="HiddenValue">
2.7.2 使用View State
所有的Web Form页面和控件都包含有一个ViewState属性,在对同一页面多次请求时可以保持页面内的值。它的内部实现是维护相应的hidden field,只不过是加密了的,所以比hidden field的安全性要好。
使用View State的性能表现很大程度上依赖于服务器控件的类型。一般来说,Label,TextBox,CheckBox,RadioButton,HyperLink的性能要好一些,而DropdownList,ListBox,DataGrid和DataList就要差很多,因为包含的数据量太大,所以每次页面回送都很耗时间。
有些情况下不推荐使用ViewState,比如:
1、 不需要回送的页面避免使用;
2、 避免使用ViewState保存大数据量;
3、 在需要使用会话超时的情况下避免使用它,因为它没有超时操作。
ViewState的性能表现和Hidden Field的是类似的,但是具有更高的安全性。
优点:
数据在页面中自动维护,不需要服务器资源;
实现简单;
数据是经过加密和压缩的,比hidden field有更高的安全性;
数据存在客户端,可以在Web Farm情况下使用。
缺点:
存储大数据量时会降低性能;
和hidden field类似,在客户端数据仍然有潜在的安全威胁。
示例代码如下:
public class ViewStateSample : System.Web.UI.Page
{
private void Page_Load(object sender, System.EventArgs e)
{
if (!Page.IsPostBack)
{
// Save some data in the ViewState property.
this.ViewState["EnterTime"] = DateTime.Now.ToString();
this.ViewState["UserName"] = "John Smith";
this.ViewState["Country"] = "USA";
}
}
…
private void btnRefresh_Click(object sender, System.EventArgs e)
{
// Get the saved data in the view state and display it.
this.lblTime.Text = this.ViewState["EnterTime"].ToString();
this.lblUserName.Text = this.ViewState["UserName"].ToString();
this.lblCountry.Text = this.ViewState["Country"].ToString();
}
}
2.7.3 使用Hidden Frame
你可以使用Hidden Frame在客户端缓存数据,这就避免了使用hidden field和使用view state时每次页面回送时的缓存数据往返。比如你可以秘密的加载多个页面所需要的图片,这并不会消耗服务器资源。
优点:
a. 可以加载较多数据而不只是单个栏位的值;
b. 避免了不必要的多次回送中的数据往来;
c. 可以缓存和读取在不同表单中存储的数据项(可以同时缓存多个页面的数据);
d. 可以访问同一站点不同frame中的客户端脚本数据。
缺点:
a. 有些浏览器不支持frame;
b. 源代码可以在客户端看到,有潜在的安全威胁;
c. 隐藏frame的数量没有限制,如果框架页面包含较多hidden frame的话,在首次加载时速度会有限制。
示例代码如下:
<FRAMESET cols="100%,*">
<FRAMESET rows="100%,*">
<FRAME src="contents_of_frame1.html">
</FRAMESET>
<FRAME src="contents_of_hidden_frame.html">
<FRAME src="contents_of_hidden_frame.html" frameborder="0" noresize scrolling="yes">
<NOFRAMES>
<P>This frameset document contains:
<A href="contents_of_frame1.html" TARGET="_top">Some neat contents</A>
</NOFRAMES>
</FRAMESET>
2.7.4 使用Cookies
Cookie是可以在客户端存储数据另一种方案,这里不过多介绍。
优点:
不需要服务器资源;数据保存在客户端,在用户请求时发送到服务器上。
使用简单。Cookie包含简单的键值对,主要保存轻量级的文本数据。
支持过期策略;可以指定当会话结束时过期,也可指定一个时间策略。
缺点:
数据量的限制;
用户可能设置为拒绝Cookie;
安全问题;用户可能更改机器上的cookie信息,造成基于cookie的系统运行失败;
可能过期或被用户删除,造成一定程度的不可用。
参看示例代码:
public class CookiesSample : System.Web.UI.Page
{
private void Page_Load(object sender, System.EventArgs e)
{
if (this.Request.Cookies["preferences1"] == null)
{
HttpCookie cookie = new HttpCookie("preferences1");
cookie.Values.Add("ForeColor","black");
cookie.Values.Add("BackColor","beige");
cookie.Values.Add("FontSize","8pt");
cookie.Values.Add("FontName","Verdana");
this.Response.AppendCookie(cookie);
}
}
private string getStyle(string key)
{
string val = null;
HttpCookie cookie= this.Request.Cookies["preferences1"];
if (cookie != null)
{
val = cookie.Values[key];
}
return val;
}
}
2.7.5 使用Query String
Query String是在用户请求的URL后加上相应的参数来使用的,只能在使用HTTP GET方式调用URL时可用。
优点:
d. 不需要服务器资源,参数附在URL里面;
e. 应用面广,几乎所有浏览器都支持;
f. 实现简单,服务端使用Request对象可直接读取。
缺点:
a. 参数直接对用户可见,不安全;
b. URL长度的限制,多数浏览器不支持超过255字符的URL。
示例代码:
http://www.cache.com/login.asp?user=ronen
string user = Request.QueryString["User"];
2.7.6 小结
下表是使用客户端缓存的建议:
缓存机制
适用情况
Hidden Field
当安全性要求不高时,在页面中存储少量数据以提交到服务器上的本页面或其它页面。
ViewState
在单个页面中存储少量信息满足页面多次回传的要求。提供基本的安全机制。
Hidden Frame
在客户端存储数据,避免了数据到服务器的回传。
Cookie
当安全性要求不高时,存储少量数据在客户端。
Query String
当使用页面地址连接页面时传输少量参数。
2.8 使用Internet Explorer缓存
IE提供了缓存机制,可以实现对页面的数据进行缓存,同时可以指定过期时间。用户在IE中请求此页面,如果当过期时间没有到,则自动从缓存中提取并呈现;否则,就到服务器上获取新版本。IE对页面的缓存可以在IIS中设置。
适合在Internet Explorer中缓存的内容
页面中的图像文件;
静态的文本内容;
页面的标题栏和页脚内容——改变频率很低,可以给用户一个迅速相应;
网站的首页——更改次数页时相对较少的;
使用动态HTML在客户端保存的特定数据。比如客户自定义的颜色和布局设置信息。
优点:
减少对服务器的请求和网络负担;
支持离线浏览;
可以实现基于XML的客户端复杂应用。
缺点:
客户端的过期时间必须预先指定而不能依赖于服务器更新;IE采用的是Lazy更新机制,优先从缓存中提取数据;
对其它客户端浏览器没有作用;
存储的数据没有加密,不能保证客户端数据安全。
示例代码:
<META HTTP-EQUIV="expires" CONTENT="Tue, 23 Jun 2002 01:46:05 GMT">
3 总结
本文档介绍了缓存和状态数据存储的相关概念,以及可供使用的缓存技术,介绍了各种技术的适用范围,并对其优缺点进行了说明,另外有简单的性能比较和简单的示例代码。更多内容请参看相应的参考资料。