1、导言
在解释asp.net Cache管理,我想澄清,不同的人使用不同的术语解释了相同的概念,即管理数据。 有人称之为状态管理,而有人称之为缓存管理.我喜欢用术语cache management,可能是因为我喜欢这个词 。不过,在概念上没有什么区别。现在让我们讨论asp.net 不同方面的缓存管理(或状态管理) 。
虽然缓存管理在windows程序环境中不是个问题,但对web环境而言却是个挑战。众所周知, HTTP协议是一个无状态的协议,web服务器不能识别不同请求的用户,当我们需要识别不同请求的特定用户并保存用户数据以便下次重新使用时,这将变的非常重要。幸运的是,asp.net提供了很多特性用来保存客户端与服务器端的会话信息,但是我们可能会比较困惑,我们到底应该使用哪项技术来实现我们的应用呢。在asp.net中,我们知道的特性,比如Session、Application、Cache Objects等,为了有效的使用这些特性,我们必须要了解不同特性有何差异以及他们的应用场合。
2、背景
在本文中,我会介绍asp.net提供的各种缓存管理方案。在web程序中,有时我们需要存储常用的数据到服务器内存,以避免每次请求都从数据库中获取数据或重复处理数据逻辑。用户请求相同的数据时将直接从内存获取,从而提高程序响应性能,缩短了用户访问时间。为了做到这点,我们需要将缓存保存到服务器端。
缓存能够帮忙我们实现3个重要方面的服务质量(QoS):
性能 - 缓存通过最小化数据获取及格式化逻辑来提高应用程序性能。
可扩展性-由于缓存最小化数据检索和格式化操作,降低了对服务器资源负荷,从而增强了可扩展性的应用。
可用性-由于应用使用数据缓存后,程式会在其他系统和数据库中避免了故障。
3、不同的选择
在web程序中,我们可以在服务器端或客户端缓存数据和页面 。让我们来看看asp.net提供的不同的选择,包括服务器及客户端来进行缓存。
4、服务器端缓存管理
asp.net会话状态:
asp.net会话状态是用来对每个请求用户进行缓存数据。这就是说,数据不能共享,多个不同用户的数据是独立的,仅限该请求用户使用。
asp.net会话状态,可管理的三种不同方式:
inproc -储存在aspnet_wp.exe进程。当进程或应用程序域执行回收后,会话数据将被清除,根据IIS的设置,一般在20分钟后。
stateserver -会话状态存储在一个单独的进程( aspnet_state.exe) ,它可以储存在一个不同的机器。因为它可以储存在不同的机器,这个选择将可以工作在Web集群的情况。
sqlserver -会话状态存储在S QLS erver数据库。这一办法也可用在Web集群的情况。
无论是在stateserver和sqlserver方案时,我们需要确保缓存对象是可序列化(Serializable)的,因为数据仓库是地地道道的工艺系统。这两个选项都要做较多的数据检索和数据逻辑处理,与选择inproc相比性能较低。所以基于我们的应用要求,我们应该选择最适合我们的要求。
下面的例子说明如何使用Session对象:
string empNum = Request.QueryString["empnum"];
if (empNum != null)
{
string details = null;
if (Session["EMP_DETAILS"] == null)
{
//Get Employee Details for employee number passed
string details = GetEmployeeDetails(Convert.ToInt32(empNum));
Session["EMP_DETAILS"] = details;
}
else
{
details = Session["EMP_DETAILS"];
}
//send it to the browser
Response.Write(details);
}
ASP.NET application对象:
实际上,应用程序状态变量是给定 ASP.NET 应用程序的全局变量。像客户端应用程序开发人员一样,ASP.NET 程序员应该始终考虑将任何对象存储为全局变量所具有的影响。
关于这一点,下面的几个问题非常重要:
- 在应用程序状态中进行存储的内存影响。存储在应用程序状态中的变量占用的内存在移除或替换该值之前不被释放,这与单独的 Web 页不同,对于 Web 页,所有资源在 Web 请求结束时都将强行释放。例如,若在应用程序状态中永久保留很少使用的 10 MB 记录集,就算不上不是对系统资源的高效使用。对于这个极端的示例,您可能发现使用 ASP.NET Cache 是更好的解决方案。
- 在多线程服务器环境中存储和访问全局变量所涉及的并发和同步问题。应用程序中的多个线程可以同时访问存储在应用程序状态中的值。您应该始终注意确保如果应用程序范围的对象是自由线程的,则它包含内置的同步支持。所有面向公共语言运行库的自定义对象都是自由线程的。如果应用程序范围的对象不是自由线程的,则您必须确保在其周围编写显式同步方法代码以避免死锁、争用状态和访问冲突。
- 在多线程服务器环境中存储和访问全局变量所涉及的可缩放性问题。只要试图写入或更新文件,就应该使用锁。保护全局资源的锁本身就是全局的,运行在访问全局资源的多个线程上的代码最终对这些锁产生竞争。这会导致操作系统在锁可用之前阻塞辅助线程。在高负载的服务器环境中,这种阻塞可能导致系统上严重的线程颠簸。在多处理器系统上,它可能导致处理器不能充分利用(因为在理论上,在等待共享锁时,可以停止某个处理器的所有线程),从而导致整体可缩放性的严重下降。
- 存储在应用程序状态中的信息的生命周期问题。在应用程序执行过程中,承载基于 .NET 的应用程序的 .NET Framework 应用程序域或进程可能会随时被强行停止并销毁,原因可能是出现了故障、进行了代码更新、重新启动了预定的进程等。因为存储在应用程序状态中的数据不是持久的,所以如果包含它的宿主被损坏,数据也将丢失。如果希望状态在这些类型的故障中保存下来,您应该将其存储在数据库或其他可持久的存储区中。
- 应用程序状态不能跨网络场或网络园共享,所谓的网络场是指应用程序由多台服务器承载的情况;网络园是指应用程序被同一台服务器上的多个进程承载的情况。这两种情况任何一个中的应用程序状态中存储的变量只对于应用程序在其中运行的特定进程是全局的。每个应用程序进程都可以具有不同的值。因此,您不能依靠应用程序状态来存储唯一值或更新全局计数器,例如在网络场和网络园情况下。
虽然存在这些问题,但是设计良好的应用程序级别的变量在 Web 应用程序中可以发挥强大的功能。您可以进行信息的一次性(即不经常的)加载和计算,然后使用应用程序状态对其进行缓存,从而在稍后的 Web 请求中可以从内存中快速对其进行访问。
例如,一个股市 Web 站点可能在一天中每 5 分钟从数据库获取大量的金融股票信息(也许是 40 MB 的数据),然后将这些信息缓存在应用程序状态中,这样所有以后的查找请求都可以在应用程序状态中访问这些信息。其结果是极大地提高了每个请求的性能,因为传入的请求不需要跨进程、跨计算机或数据库的往返过程。另一方面,对大的、瞬态的数据块来说,使用资源较好的方法可能是使用缓存。
asp.net提供了Application对象来存储数据,针对所有的用户请求都有效,一般用来保存应用程序全局数据。这个对象的生命周期与应用程序联系在一起,当应用程序重启时该对象将被重建。不像asp.net Session对象,这个对象是向所有用户请求都可用。由于这种存储是创造和维持在一个应用程序域之内,所有它不能用在WEB集群的情况。这个对象是非常有用的,与应用程序配置文件类似,在应用启动时加载,不用每次用户请求时都装载它。但是,对于存储在该对象的数据并没有提供到期后自动废除功能,将一直保存在服务器内存直到应用程序被销毁。所以在这种情况下,可使用其他方案,如asp.net Cache对象,下面是这个对象的介绍。
ASP.NET cache object
ASP.NET Cache对象是用的比较多的缓存机制。它提供一个键值对对象用来存储数据,该对象可在命名空间System.Web.Caching下找到。该对象也是在应用程序域范围内有效,生命周期与应用程序关联在一起。跟Session对象不一样,它是针对所有用户请求都可用。
尽管Application对象和Cache对象看上去似乎很像,但他们之间的关键不同,在于后者提供了缓存数据的到期策略和文件依赖性。这就是说,当存储的缓存过了设定的时间或者它依赖的文件发生改变时,该缓存将被自动移除。
下面我们来看看Cache对象的到期策略及其依赖性。
依赖性:
依赖性意味着当依赖的记录发生改变时,缓存的项可以自动移除。缓存的依赖关系可以定义在任意缓存项中,ASP.NET支持2种不同的依赖方式:
文件依赖:某个缓存依赖于指定的文件,当文件被修改时,该缓存项自动移除。比如:在我的项目中,要通过模板生成静态页面,这时我将模板文件读入到缓存中,这样就不用每次获取模板时都要读取模板文件,而我手工修改了模板时,如果不使用依赖,程序依然从缓存获取模板,而我希望的是得到修改后的模板。
if( !cache.Exists("ERROR_INFO") ){ //如果缓存不存在
object errorData;
//Load errorData from errors.xml
CacheDependency fileDependency = new CacheDependency(Server.MapPath("errors.xml"));
Cache.Insert("ERROR_INFO", errorData, fileDependency);
}
else{...//直接从缓存取 }
Key依赖:与文件依赖的不同之处在于依赖项不是文件,而是依赖于另外一个缓存项。当依赖的缓存项改变后,所有依赖该项的缓存都将被移除。比如雇员号、雇员名称、雇员地址都添加到了缓存中,当雇员号发生改变或删除时,相关的缓存将被删除。
到期策略:
到期策略是定义缓存如何和何时到期。基于时间的到期机制:通过预定义一个时间来判断是否过期。指定的到期时间可以为一个绝对时间,或一个时间段(过多长时间过期):
//绝对过期时间
Cache.Insert("EMP_NAME", "Shubhabrata", null,
DateTime.Now.AddDays(1), Cache.NoSlidingExpiration);
//滑动到期时间
Cache.Insert("EMP_NAME", "Shubhabrata", null,
Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(60));
如何知道一个缓存项已经被删除?
在上面的例子中,我们已经知道了如何删除一个缓存项,但是有时我们想知道缓存什么时候被删除了?
我们知道了何时被删除有什么用呢?如果我们需要在删除缓存后再做点什么事情呢。
asp.net中提供了回调功能,是的,删除缓存时,当然也可以回调一个指定的方法。
private void AddItemsToCache()
{
int empNum = 5435;
CacheItemRemovedCallback onEmpDetailsRemove =
new CacheItemRemovedCallback(EmpDetailsRemoved);
Cache.Insert("EMP_NUM", empNum, null,
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
onEmpDetailsRemove);
}
private void EmpDetailsRemoved(string key, object val,
CacheItemRemovedReason reason)
{
//When the item is expired
if (reason == CacheItemRemovedReason.Expired)
{
//Again add it to the Cache
AddItemsToCache();
}
}
在上面的例子中,你必须注意到参数cacheitempriority。 cacheitempriority是用于设置优先执行的项目,我们都加入到高速缓存。优先级告诉服务器在低内存时,优先保留的缓存。
.Net Remoting
你可能会想,.net remoting能用来做数据缓存吗?当我第一次听说时,也是如此。你知道的,.net remoting的单例对象在多个客户端可以共享相同的实例,所以单例对象可以用来存储和共享不同客户端的调用。而且.net remoting能够远程方法调用,独立于进程与机器之外,所以它能够适用于WEB群集的情况。
使用.net remoting缓存,我们可将缓存对应到单例对象的成员变量上,并公开方法来读取和写入。但是在实施该方式时,必须将远程对象的租赁期限设为无期,以避免缓存被垃圾回收器回收。可通过覆盖MarshalByRefObject对象的InitializeLifetimeService方法并返回 null。
但是这种方法的唯一缺点就是性能比较低下,相对于其他实现方法而言,所以对于采用哪个方法实现缓存是要根据不同的应用环境来定。
静态变量:
We use static variables for storing data or objects globally so that it can be accessed during the life of the application. Similarly, in ASP.NET we can use static objects for caching data and we can also provide methods to retrieve and save data to the cache. As static variables are stored in the process area, performance wise it is faster. But since it is very difficult to implement expiration policies and dependencies incase of static variables, I generally prefer ASP.NET cache object over this option. Another problem is that the custom static cache object has to be thread-safe which has to be implemented carefully.
Custom static cache object can be defined as shown below: 使用public静态成员变量来存储数据,静态变量是全局的,所以在应用程序生命周期内都可以随时访问。同样,在asp.net中,我们也可以静态成员来缓存数据,当然也要提供方法对数据进行读取。静态成员存储在进程内存区,性能相对快。但很难对静态成员实施过期策略及依赖性,需要自己编程实现,所以我个人宁愿使用Cache对象。另外一个问题是自定义的静态缓存对象必须是线程安全的,所以实施时要注意。
自定义静态成员变量
作用范围:所有用户
作 用 域:public/private等
生命周期:静态成员第一次被访问到应用程序结束
自定义的静态Cache对象可参考下面的代码定义:
public class CustomCache
{
//Synchronized to implement thread-safe
static Hashtable _myCache =
Hashtable.Synchronized(new Hashtable());
public static object GetData(object key)
{
return _myCache[key];
}
public static void SetData(object key, object val)
{
_myCache[key] = val;
}
}
Database:
我们也可以使用数据库在多个用户或机器间存储和共享数据。这种方式对于需要缓存大量数据时非常有用,不适用缓存小量数据,因为这样做对性能毫无提升。当数据需要存储在数据库中时,最好将缓存的对象设置为能够序列化的,这样做能方便缓存对象的存取。比如,把对象XML格式序列化,或二进制格式序列化。
ASP.NET 页面 output caching
对于有些页面很少修改的情况,1个月或更长时间都不修改,可以将页面输出缓存起来。但有些动态页面是有参数的,asp.net中也提供了根据参数进行页面缓存。
<%@ OutputCache Duration="60" VaryByParam="None" %>
%@OutputCache Duration="60" VaryByParam="empNum" Location="Server"%
下面我们比较一下以上各种方法的特点:
方式 | 适用于web群集? | 备注 |
ASP.NET Session State |
| 只存储用户会话相关的数据 |
ASP.NET Application Object | No | |
ASP.NET Cache Object | No | |
.NET Remoting | Yes | |
Memory-Mapped files | No | |
Static Variables | No | |
Database | Yes | |
ASP.NET Page Output Caching | No |
客户端缓存管理:
Cookies
ViewState
隐藏字段Hidden Fields