.net环境下的缓存机制

.NET
503
0
0
2022-03-22

?.NET环境下的缓存技术

摘要: 介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制和适用范围说明,设计缓存方案应该考虑的问题,以及.NET环境下可用的缓存技术.

1   (*)概念

1.1  缓存能解决的问题

u性能——将相应数据存储起来以避免数据的重复创建、处理和传输,可有效提高性能。比如将不改变的数据缓存起来,例如国家列表、市区列表等,能明显提高web程序的反应速度;

u稳定性——同一个应用中,对同一数据、逻辑功能和用户界面的多次请求经常发生。当用户基数很大时,如果每次请求都进行处理,消耗的资源是很大的浪费,也同时造成系统的不稳定。例如,web应用中,对一些静态页面的呈现内容进行缓存能有效的节省资源,提高稳定性。而缓存数据也能降低对数据库的访问次数,降低数据库的负担和提高数据库的服务能力;

u可用性——有时,提供数据信息的服务可能会意外停止,如果使用了缓存技术,可以在一定时间内仍正常提供对最终用户的支持,提高了系统的可用性。

 1.2  理解状态

  缓存可以说是状态管理的框架。理解状态的含义和它的一些特性——比如生存期和生存范围——对决定是否缓存和选择合适的缓存技术有很大帮助。状态是指一些数据,在应用系统某个时间点上,数据的状态和条件。这些数据可能是永久性的存储在数据库中,可能是只在内存里停留一会,也可能是按照某个逻辑存活(比如多长时间后释放),它的应用范围可能是所有用户可访问,可能是单个用户有权限;

1.2.1 状态的生存期

生存期是指数据保持有效性的时间区间,也就是从创建到移除的时间间隔。

通常的生存期有以下几种:

Ø永久状态Permanent State——应用程序使用的永久数据;

Ø进程状态Process State——只在进程周期内有效;(进程的三种状态:运行、阻塞、就绪)

Ø会话状态Session State——和特定的用户会话有关;

Ø消息状态Message State——处理某个消息的时间内有效;

1.2.2 状态的范围

 状态的范围指对该状态有访问权限的物理或逻辑范围。

Ø物理范围指可以被访问到的状态数据存放的物理位置,通常包括:

1、 组织Organization——在一个组织内的所有应用程序可以访问状态数据;

2、 场Farm——在应用场范围内的任何机器上都可以访问;(WEB场:许多服务器共同承担响应用户申请的责任,用户的请求并不总是被路由到同一个服务器,而是被称为”负载平衡”进程的特殊软件对URL站点的申请分配任意一个空闲的服务器.)

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 理解状态数据的转换过程

 状态的另一个属性是在不同阶段的表现形式。在数据库中存储的是原始格式的数据,业务流程中的是处理过的数据,给最终用户呈现的则是另外的形式。如下表所示:

 决定缓存数据时,应该考虑缓存哪个阶段(哪种形式)的状态数据。以下方针有助于做决定:

Ø当业务逻辑可以容忍缓存数据的陈旧时就缓存原始数据;原始数据可以缓存在数据库访问组件和服务代理中;

Ø缓存处理过的数据以减少处理时间和资源;处理过的数据可以缓存在业务逻辑组件和服务接口中。

Ø当需要呈现的数据量很大并且控件的呈现时间很长时,缓存呈现数据(比如包含大数据量的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 (*) 管理

  缓存数据时,应用系统需要的维护工作加大了。在发布应用程序时,需要對緩存配置相应的属性,比如缓存的大小限制和清除策略。同时要使用某种机制来监控缓存的效率(比如事件日志和性能计数器)

2   缓存技术

下面主要介绍以下技术:

Ø使用Asp.Net缓存;

Ø使用内存映射文件;

Ø使用SQL Server缓存;

Ø使用静态变量缓存;

Ø使用Asp.net 会话状态(Session State);

Ø使用Asp.net客户端缓存和状态;

2.1  Asp.net缓存

    ASP.NET提供了两种基本的缓存机制来提供缓存功能。一种是应用程序缓存,它允许开发者将程序生成的数据或报表业务对象放入缓存中。另一种是页输出缓存,可以直接获取存放在缓存中的页面,而不需要经过繁杂的对该页面的再次处理。

   在.Net Framework中,应用程序缓存通过System.Web.Caching.Cache类实现。它是一个密封类,不能被继承。对于每一个应用程序域,都要创建一个Cache类的实例,其生命周期与应用程序域的生命周期保持一致。每当应用程序启动的时候就重新创建Cache对象。它和Application对象的主要区别就是提供了专门用于缓存管理的特性,比如依赖和过期策略。

   可以使用Cache对象和它的属性来实现高级的缓存功能,同时可以利用Asp.net Cache来对客户端输出的响应内容进行缓存。

   应用程序缓存是通过ASP.NET管理内存中的缓存空间。放入缓存中的应用程序数据对象,以键值对的方式存储,这便于用户在访问缓存中的数据项时,可以根据key值判断该项是否存在缓存中。

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)——指定缓存中的某个数据项更改时移除。如:

 //创建一个缓存条目

Cache["key1"] = "Value 1";

 //创建key2依赖于key1

String[] dependencyKey = new String[1];
dependencyKey[0] =“key1”;

 //创建CacheDependency类关联缓存依赖项的实例

CacheDependency dependency = new CacheDependency(null, dependencyKey);
Cache.Insert("key2", "Value 2", dependency);

·  基于时间的过期策略——来使数据失效,可以是绝对时间也可以是相对现在的相对时间。

 如:

 /*绝对过期(NoAbsoluteExpiration可以设置缓存的绝对过期时间*/

 Cache.Insert("CachedItem", item, null,Cache.NoSlidingExpiration, DateTime.Now.AddSeconds(30));

 /*相对过期(NoSlidingExpiration设置相对过期时间,如果缓存在设定的时间内没有被访问,缓存过期;如果有访问,则缓存过期时间将会重置为原始值*/

 Cache.Insert("CachedItem", item, null, Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(20));

 使用太短和太长的过期时间都不行,不是造成用不上的缓存数据,就是缓存了陈旧的数据并加重了缓存负担,所以可以使用高并发的测试来决定过期时间的最佳值。

2.1.2 实现对数据库的依赖

    这就要求实现通知机制,当数据库数据改变时能够通知你的缓存数据改变

SqlCacheDependency说简单些就是当数据库的数据发生改变的时候使依赖于此数据库(表)的缓存自动过期。

    SqlCacheDependency又分为Sql Server2000的基于轮询(Polling)的拉机制,和Sql Server2005 基于通知(Notification)的推机制两种不同策略。而应用System.Web.Caching.Cache时不能应用SqlCacheDependency过期策略,只能应用基于某个文件改变或其他Cache项目改变的过期策略。

    (轮询(Polling)机制意思是说Asp.Net进程每隔一段时间就对数据库进程进行一次访问,因为间隔时间是固定的,所以叫轮询(访问时间以毫秒为单位,可以在Web.Config里设置)。当某次轮询时发现与上一次访问时的数据不一样,那么就立刻使依赖于此数据的缓存过期。

    通知(Notification)机制是说Asp.Net只管做自己的事情,不对数据库进程进行询问,而当数据库的数据发生变动时,进程主动通知Asp.Net进程,数据发生了改变,然后Asp.Net让缓存过期。由此可见,使用Sql Server2005 的通知机制效率要高得多。)

数据库上的ASP.NET缓存依赖

 利用SQL Server的系统存储过程sp_makewebtask ,是可以达到这个目的,在触发器中使用它,可以取得一个合理有效的途径使在CacheDependency实例中的文件监视进程侦测到文件的变化,从而使缓存失效。事实上,因为CacheDependency 类工作在UNC文件协议上,可以在整个Web Farm上部署这个解决方案,Web Farm上每台机器的应用程序副本都会通过UNC文件路径来监视WebFarm中某台单个机器上的同一个文件 .

 (通用命名规则 (UNC):一种对文件的命名规则,它提供了独立于机器的文件定位方式。

 UNC 名称使用 \\server\share\path\filename 这一语法格式,而不是指定驱动器符和路径。)

2.1.2 .1 使用SQL Server中信赖的Northwind范例数据库。创建一个简单的DataGrid来显示Employees表中的记录.

1.创建触发器

CREATE TRIGGER WriteCacheDepFile ON [dbo].[Employees]

FOR INSERT, UPDATE, DELETE

AS

EXEC sp_makewebtask '\\peter\C$\Cache\mycache.txt', 'SELECT top 1 FirstName FROM employees'

 存储过程就是告诉SQL Server,如果Employee表发生任何变动,就根据一个简单的查询来更新”mycache.txt”文件,只要它是一个有效的T-SQL语句,SQL Server就会去更新那个文件。

2.创建一个目录,并设为共享。

 可能要更新该文件的访问权限,以使它可以被写入。

 注意,使用的是管理员共享”C$”.

3.创建一个空的文本文件

 在创建的共享目录下,创建文本文件 "mycache.txt".

4.创建用程序

 首先,在web.config文件中输入依赖文件名称,这样做可以使在修改依赖文件的时候不需要重新部署应用程序。

在web.config文件的根部,添加appSettings配置节:

  <appSettings>

//缓存依赖文件路径

<add key="dependencyFile" value="\\peter\Cache\mycache.txt" />
</appSettings>
</configuration>

5.在Global类中建立缓存机制 

 这样就不需要在任何页面中编写特定的代码.

在Global类中建立缓存机制

public class Global : System.Web.HttpApplication
{
    Cache _cache =null;
    public static bool blnReflash = false;
    public const string ConnStr = "server=localhost;database=Northwind;uid=sa;pwd=";
    public const string strSQL = "SELECT EmployeeID, lastname, firstname FROM Employees";
    protected void Application_Start(Object sender, EventArgs e)
    {
        _cache = Context.Cache; //定义一个Cache类型的_cache对象,把Cache实例赋给它
        RefreshCahe(null,null,0); // 调用RefreshCache方法填充该对象
    }
    protected void Session_Start(Object sender, EventArgs e)
    { 
        if(HttpContext.Current.Cache["Employees"]==null)
        RefreshCache(null,null,0); //调用RefreshCache方法填充缓存,確保可用
    } 
    // RefreshCache静态的委托回调方法,被监控的文件变化时,即缓存失效时,调用该委      托刷新缓存中的数据  
    static void RefreshCache(string key,object item,CacheItemRemoveReason reason)
    {
        SqlDataAdapter adapter = new SqlDataAdapter(strSQL,ConnStr);
        DataSet ds = new DataSet();
        adapter.Fill(ds,"Employees");
        CacheItemRemovedCallback onRemove = new CacheItemRemovedCallback(RefreshCache);
        string depFile= ConfigurationSettings.AppSettings["dependencyFile"].ToString();
        HttpContext.Current.Cache.Insert("Employees",ds,new CacheDependency(depFile),
        Cache.NoAbsoluteExpiration,Cache.NoSlidingExpiration,
        CacheItemPriority.High,onRemove);
        blnReflash = true;
    }
}

  RefreshCache实际上是一个静态的委托回调方法,就是简单的从Empoyees表中取得一个DataSet,然后。创建CacheItemRemovedCallback类型的委托OnRemove,使其指向RefreshCache方法,当被监控的文件变化时,也就是缓存失效时,就会调用该委托,刷新缓存中的数据。

最后把DataSet连同OnRemove委托一起插入到缓存中,在Session_Start中,为了“保险“,另外添加了一个判断来调用RefreshCache方法填充缓存。

  应用程序就创建好了,在任何页面中都可以访问到缓存的DataSet。

 在WebForm1的aspx.cs中,如何使用它呢?

private void Page_Load(object sender, System.EventArgs e)
{
    //保证缓存非空,如果为空,则填充它  
    if(Cache["Employees"] == null)
    {
        Global.RefreshCache(null,null, 0);
        cacheStatus.Text = "Cache Refreshed at "+ DateTime.Now.ToLongTimeString();
    }
    else
    cacheStatus.Text = " DataSet from Cache ";
    DataSet ds = (DataSet)Cache["Employees"];
    DataGrid1.DataSource= ds.Tables[0];
    DataGrid1.DataBind();
}

现在,如果请求这个页面,它将会每次成功的显示从从Cache中取得的DataSet

或者是:不在Global类中建立,在web.config里面添加一些内容就可以使用了。

1、首先添加数据库连接串:

<connectionStrings> 
    <add name ="pubsConString" connectionString="server=localhost;database=pubs;uid=sa;pwd=mypassword;"/>
</connectionStrings>
2、其次添加caching节:
<system.web> 
    <caching> 
        <sqlCacheDependency enabled="true"> 
            <databases> 
                <add name="pubs" connectionStringName="pubsConString" pollTime="900"/> 
            </databases> 
        </sqlCacheDependency> 
    </caching>
</system.web>

下面是测试代码,当页面加载的时候执行如下代码:

protected void Page_Load(object sender, EventArgs e)
{ 
    //由于数据会过期,所以当使用缓存中的数据时,必须检查数据的有效性。  
    if (HttpContext.Current.Cache["xxx"] == null)
    {    
        SqlCacheDependency d = new SqlCacheDependency(“pubs”,“authors”); //pubs在databases节指定
        HttpContext.Current.Cache.Insert("xxx", "xxx", d);
        Response.Write("Create new cache value is xxx.");
    }
    else
    {
        Response.Write("Load data from cache,value is "+HttpContext.Current.Cache["xxx"].ToString());
    }
}

2.1.1.2 使用缓存回调(Cache Callback)

Cache的添加方法有Add()或Insert(),两种方法几乎类似,只是Inser方法可以使用可选参数,即使用默认参数,来实现缓存的添加;可以定义回调,当移除自动发生时, 可以不移除它或者使用新的数据来替换它。如:

CacheItemRemovedCallback onRemove = new CacheItemRemovedCallback(this.RemovedCallback);

// 实施函數RemovedCallback(缓存名, 要缓存的对象, 缓存过期表達式),以处理到期的缓存
public void RemovedCallback(string key, object value, CacheItemRemovedReason r)
{
    // 测试项目是否已过期,并重新把它缓存  
    if (r == CacheItemRemovedReason.Expired)
    {
        // 重新把它再次缓存。
        CacheItemRemovedCallback onRemove = null;
        onRemove = new CacheItemRemovedCallback(this.RemovedCallback);
        Cache.Insert(
          "CachedItem", //缓存名
          item, //要缓存的对象 
          null, //依赖项
          AbsoluteExpiration, //绝对过期时间
          SlidingExpiration, //相对过期时间
          CacheItemPriority.Default, //优先级
          onRemove); 
      //缓存过期引发事件
    }
}

例如:定义回调,当移除自动发生时, 不移除它使用新的数据来替换它。

public bool GetPassWord(string MailTo, string UserName, string UserAccount, string PassWord,
string UrlHome, string MailTemplatePath)
{
    bool Ret = false;
    string MailTemplate = string.Empty;
    StreamReader sr = null;
    if (System.Web.HttpRuntime.Cache["GetPassWordToUser"] != null)
    MailTemplate = System.Web.HttpRuntime.Cache["GetPassWordToUser"].ToString();
    else  //以默认的编码方式响应文件流对象
    { 
        sr = new StreamReader(MailTemplatePath, Encoding.Default);
        MailTemplate = sr.ReadToEnd();
        System.Web.HttpRuntime.Cache.Insert("GetPassWordToUser", MailTemplate);
        if (sr != null)
        {  
            sr.Close();
            sr.Dispose();
        }
    }
    if (!string.IsNullOrEmpty(MailTemplate))
    {
        MailTemplate = MailTemplate.Replace("{HomePage}", UrlHome.TrimEnd('/') + "/");
        ……//同样取代过期的UserID、UserName、Password…值
        Common clsCom = new Common();
        MailLib cls = new MailLib();
        cls.MailTo = MailTo;
        cls.MailBody = MailTemplate;
        ……// cls.MailTitle、cls.MailFrom、cls.Host、cls.UserID、cls.Password…赋值  
        try
        {
            Ret = cls.SendMail();
        }
        catch
        { }
    }
    return Ret;
}

2.1.1.3 (*) 对缓存项使用优先级

   当运行应用程序的服务器内存不足时,会自动清除缓存中的数据,称为“清除scavenging”。此时,Cache对象根据缓存项的优先级来决定先移除哪些缓存数据,可以在代码中指定缓存项的优先级。如:

 Cache.Insert("DSN", connectionString, null, d, t, CacheItemPriority.High, onRemove);

  当向缓存添加项时,可以为其分配与缓存中存储的其他项相比较的相对优先级。在服务器处理大量请求时,分配了较高优先级值的项被从缓存删除的可能性较小,而分配了较低优先级值的项则更有可能被删除。默认值为Normal。

 CacheItemPriority 类的成员名称及其说明

成员名称          说明

AboveNormal 被删除的可能性比Normal优先级的项要小。

BelowNormal    比分配了Normal优先级的项更有可能被从缓存删除。

Default        缓存项优先级的默认值为Normal。

High           最不可能被从缓存删除。

Low            最有可能被从缓存删除。

Normal         被删除的可能性仅次于具有Low或BelowNormal优先级的那些项

NotRemovable   将不会被从缓存删除。

2.1.1.4刷新数据(清除缓存)

 缓存清除是指从内存中移除缓存数据。没有直接的方法来刷新Asp.net的输出缓存,但是有替代方法(设置所有数据失效),比如:Response.Cache.SetExpires(DateTime.Now)

 缓存清除的3个原因:

   一是缓存项数据过期。过期的缓存项数据都必须被删除,否则导致服务器内存不足。

   二是缓存依赖项发生改变。依赖项与数据缓存项有着密切关系。根据应用程序设置,如果依赖项发生改变,数据缓存很可能会被清除。

   三是服务器内存不足,开始缓存清理过程。如果某些项在一段时间内未被访问,或是在添加到缓存中时被标记为低优先级,则这些项会被移除。

2.1.2 输出缓存(Output Cache)

 缓存需要传输和显示到客户端浏览器上的数据的两种方式——页面输出缓存(Page Output Cache)和页面片断缓存(Page Fragment Cache)。当整个页面相对变化较少时,可以缓存整个页面;如果只是页面的一部分经常变化,可以使用片断缓存。

 可以通过@OutputCache指令完成对Web页面的输出缓存。它主要包含两个参数:Duration和 VaryByParam。Duration参数用于设置页面或控件进行缓存的时间,其单位为秒。如下的设置表示缓存在60秒内有效:

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

   只要没有超过Duration设置的期限值,当用户访问相同的页面或控件时,就可以直接在缓存中获取。

2.1.2.1 页面输出缓存

   Page Output Caching将对页面请求的响应放入缓存中,后续对此页面的请求将直接从缓存中得到信息而不是重建此页面。可以通过添加Page指令(高级别,声明实现)来实现,也可以使用HTTPCachePolicy类来实现(低级别,程序实现)。

下面介紹如何更好使用Page Output Caching和最佳实践。有四方面的内容:

 1、(*) 决定缓存的内容

页面输出缓存可缓存各种信息,缓存这些信息意味着不需要经常处理同样的数据和结果,包括:

?经常被请求但不改变的静态页面;

?更新频率和时间已知的页面(如显示股票价格的页面);

?根据HTTP参数,有几个可能输出的页面(如根据城市的代号显示该城市天气情况的页面);

?从Web Service返回的结果;如:

[WebMethod(CacheDuration=60)]

 // Web支持Web Service返回结果缓存, CacheDuration指定缓存时间

public string HelloWorld()
{
    return "Hello World";
}

/*WebMethod 6个属性: Description(是对webservice方法描述的信息)、EnableSession(指示webservice是否启动session标志)、MessageName(实现方法重载后的重命名)、TransactionOption(指示 XML Web services 方法的事务支持)、CacheDuration.BufferResponse (配置WebService方法是否等到响应被完全缓冲完,才发送信息给请求端)*/

2、 缓存动态页面

      基于输入参数、语言和浏览器类型改变的动态网页经常用到。可以使用OutputCache的以下属性来实现对动态页面的缓存:

ØVaryByParam——基于输入参数不同缓存同一页面的多个版本;

ØVaryByHeader——基于Page Header的内容不同缓存页面的多个版本;

ØVaryByCustom——通过声明属性和重载GetVaryByCustomString方法来定制缓存处理页面的多个版本;

ØVaryByControl——基于控件中asp对象属性的不同来缓存控件。

对多个版本页面的缓存会降低可用内存,所以要衡量缓存策略。

使用VaryByParam参数可以根据设置的参数值建立不同的缓存。

例如:在一个输出天气预报结果的页面中,如果需要为一个ID为txtCity的TextBox控件建立缓存,其值将显示某城市的气温,那么我们可以进行如下的设置:

<%@ OutputCache Duration=”60” VaryByParam=”txtCity” %>

ASP.NET会对txtCity控件的值进行判断,只有输入的值与缓存值相同,才从缓存中取出相应的值。这就有效地避免了因为值的不同而导致输出错误的数据。

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)
{ 
    //使页面输出缓存
    Response.Cache.SetCacheability(HttpCacheability.Server);
    //设定的时间参数,以20秒
    Response.Cache.SetExpires(System.DateTime.Now.AddSeconds(20));
    //设置标题参数
    Response.Cache.VaryByHeaders["Accept-Language"] = true;
    //设置缓存参数'state'
    Response.Cache.VaryByParams["state"] = true;
    //设置的自定义参数' minorversion '
    Response.Cache.SetVaryByCustom("minorversion");
}

2.1.2.2 页面片断缓存

有时候缓存整个页面并不灵活,同时内存的负担也比较大,这时候应考虑片断缓存。

页面片断缓存适合以下类型的数据:

a.创建开销很大的页面片断(控件);

b.包含静态数据的页面片断;

c.可被多个用户使用的页面片断;

d.多个页面共享的页面片断(如公用菜单条)

自定义控件中加入缓存,从而实现整个页的局部缓存。

自定义控件在前台加入:

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

后台代码:

protected void Page_Load(object sender, EventArgs e)
{
    Label1.Text = DateTime.Now.ToString();
    //从HttpRuntime.Cache读缓存项
    source = (DataView)Cache["SQUARE"];
    if (source == null)
    {
        //直接从数据库从读取
        conn = new SqlConnection(ConfigurationManager.ConnectionStrings["GoConnectionString"].ConnectionString);
        mycmd = new SqlDataAdapter("select * from GUser", conn);
        DataSet ds = new DataSet();
        mycmd.Fill(ds, "GUser");
        source = new DataView(ds.Tables["GUser"]);
        //并将结果缓存到HttpRuntime.Cache中  
        Cache["SQUARE"] = source;
    }
    GridView1.DataSource = source;
    GridView1.DataBind();
}

5、(*)@OutputCache指令指定缓存位置的OutputCacheLocation属性的枚举值如:

2.4 (*)  使用SQL Server缓存

   如果需要在进程回收(重启)、机器重启或电源故障的过程中保持缓存数据的有效,基于内存的方案并不能满足要求。可以使用基于永久数据存储的方案,如SQL server数据库或NTFS文件系统。

 (NTFS文件系统是一个基于安全性的文件系统,是Windows NT所采用的独特的文件系统结构,它是

  建立在保护文件和目录数据基础上,同时照顾节省存储资源、减少磁盘占用量的一种先进的文件系统。)

  SQL Server在使用sql语句或存储过程得到数据时,使用.Net 框架提供的Ado.Net 、SQLDataAdapter对象来访问datatable或dataset。

使用SQL Server缓存数据的优点:

ü易于实现——使用.Net 框架和Ado.Net访问数据库相当方便;

ü完善的安全模型和很高的健壮性;

ü数据非常方便的共享;

ü数据的持久保留。

ü支持很大的数据量。

ü方便的管理工具

缺点:

?需要安装SQL Server,对小型应用来说不合适;

?重新构造数据的性能和读取数据库的性能比较低;

?网络负担。

2.5  (*) 使用静态变量缓存

    静态变量常用来记录类的状态,可以用它来创建定制的缓存对象。在定制的缓存类中将数据存储器声明为静态变量,并且提供维护接口(插入、删除和访问等)。由于是在内存中,这种方案可提供对缓存数据的直接、高速的访问,当没有替代方案解决键值对的存储且对速度要求很高时,可以使用静态变量。asp.net中,应该使用Cache对象。可以保存大数据的对象,前提是它不经常更改。由于没有清除机制,大数据的内存消耗会影响性能。需要保证定制线程安全机制,或者使用.Net框架提供的同步对象,

  比如使用Hashtable实现:

   static Hashtable mCacheData = new Hashtable();

    (应用范围:該方案的应用范围可以限制到类、模块或整个项目。如果变量定义为public,整个项目中的代码都能访问它,范围是整个应用程序域,实现了高效的共享。而它的生存期是和范围紧密相关的。)

2.6    使用asp.net session state

   可以使用基于HttpSessionState对象的asp.net session state来缓存单个用户的会话状态信息。当稳定性和可用性要求很高时,asp.net session state虽然效果不好,但对比较小的单个值scalar Value(比如登录信息),还是很有效。

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数据库中。

  调整配置文件中标签的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模式(使用一个名为 state server 的机器用它的内存来存放其他机器的session 状态)。StateServer模式使用指定的进程储存状态信息。因为它也是一种进程外模式,要保证存储的对象是可序列化的,以支持跨进程传输。

<machineKey validationKey="AutoGenerate|value[,IsolateApps]"
           decryptionKey="AutoGenerate|value[,IsolateApps] ”validation="SHA1|MD5|3DES"/>

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的方式下,才会进行序列化操作;如:

public static byte[] Model2Binary(object obj)
{  
    byte[] result = new byte[0];
    //創建BinaryFormatter类的新实例
    BinaryFormatter binaryFormatter = new BinaryFormatter();
    //创建内存流对象
    MemoryStream memoryStream = new MemoryStream();
    //以二进制格式将对象序列化和反序列化
    binaryFormatter.Serialize(memoryStream, obj);
    if (memoryStream.Length > 0)
    {  
        result = new Byte[memoryStream.Length];
        memoryStream.Read(result, 0, result.Length);
    }
    memoryStream.Close(); return result;
}

3、  缓存的安全问题,当在缓存中存储敏感数据时,需要考虑安全性,其它页面可以访问到缓存中的数据;

4、  避免缓存大数据,那会降低性能;

5、  Session对象缓存方式不支持过期策略、清除和依赖。

2.6.3 用System.Web.Caching.Cache 保存数据操作类的缓存

public class DataProvider //采用了一个简单工厂来做数据访问,实现多数据库操作的方式
{   
    public static IDataProvider Instance()
    {   //使用高速缓存  
        Cache cache = System.Web.HttpContext.Current.Cache;
        if ( cache["IDataProvider"] == null )
        {                 //从Web.config中获得程序集路径和类名  
            String prefix = "";
            //NameValueCollection 类:可通过键或索引访问的关联String键和String值的已排序集合  
            NameValueCollection  context =    (NameValueCollection)ConfigurationSettings.GetConfig("appSettings");
            if (context == null)
            { 
                return null; //不能设置 
            }
            String assemblyPath = context[prefix + "DataProviderAssemblyPath"];
            String className = context[prefix + "DataProviderClassName"];
            // 以虚拟的形式配置的路径,必须转换为物理路径
            assemblyPath =   HttpContext.Current.Server.MapPath(HttpContext.Current.Request.ApplicationPath + "/bin/" + assemblyPath);
            try       // 使用反射存储的构造类,实现更新供应商
            {         //映射到assemblyPath的dll下className 的类,把这个类反射到Assembly中
                cache.Insert( "IDataProvider",
                Assembly.LoadFrom(assemblyPath).GetType( className ).GetConstructor(new Type[0]),
                new CacheDependency(assemblyPath ) );
            }
            catch (Exception)
            {                // 无法找到DLL文件
                HttpContext.Current.Response.Write("ERROR: Could not locate file: <code>" +
                    assemblyPath + "</code> or could not locate class <code>" + className + "</code> in file.");
                HttpContext.Current.Response.End();
            }
        }
        return (IDataProvider)(((ConstructorInfo)cache["IDataProvider"]).Invoke(null) );
    }  
    // ConstructorInfo类: 发现类构造函数的属性并提供对构造函数元数据的访问  
    // Invoke函数:用於子类对其父类构造函数的调用,也就是调用已有对象的构造函数
}

2.6.3 实现Session State

 Asp.net提供了简单接口来操作Session State,并可使用Web.Config进行简单设置,

以下代码演示了使用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"
/>

   //後台頁面使用 Session對象

Session["ShoppingCartID"] = CartID; 

string CartID = (string)Session["ShoppingCartID"]; 

2.6.4 在非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;

   ( 进程管理信息进程文件: ASPNET_WP or ASPNET_WP.exe

     ASPNET_WP.exe是涉及Microsoft asp.net技术的程序运行所必须的程序,aspnet_wp.exe进程是

    用来解析aspx等文件的。)

2.7  (*) 使用Asp.net客户端缓存和状态

 可以使用客户端存储页面信息的方式来降低服务器的负担,提供最低的安全保障,却有最快的性能表现。由于需要将数据发送到客户端存储,所以数据量有限。

实现客户端缓存的机制有以下五种,接下来将依次介绍:

Ø隐藏栏位(Hidden Field)

ØView State

Ø隐藏帧(Hidden Frame)

ØCookies

ØQuery String

五种方式分别适合于存储不同类型的数据。

2.7.1 使用Hidden Field

 将经常改变的少量数据保存在HtmlInputHidden中来维护页面的状态。当页面回送的过程中,这些数据都会包含在表单中送到服务器,所以要使用HTTP POST方式来提交页面。

使用这种方式的优点如下:

ü不需要服务器资源,直接从页面中读取;

ü几乎所有的浏览器都支持;

ü实现简单;

ü由于数据在页面中,所以在web Farm的情况下也可使用。

缺点:

?由于可以通过查看源码看到,所以可能会被篡改;

?不支持复杂格式的数据,复杂数据必须使用解析字符串的方式来间接得到;

?当存储大数据的时候会影响性能。

2.7.2 使用View State

   所有的Web Form页面和控件都包含有一个ViewState属性,在对同一页面多次请求时可以保持页面内的值。

   使用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类似,在客户端数据仍然有潜在的安全威胁。

2.7.3 使用Hidden Frame

 可以使用Hidden Frame在客户端缓存数据,避免使用hidden field和view state时每次页面回送时的缓存数据往返。比如可以秘密的加载多个页面所需要的图片,这并不会消耗服务器资源。

?优点:

a.    可以加载较多数据而不只是单个栏位的值;

b.    避免了不必要的多次回送中的数据往来;

c.    可以缓存和读取在不同表单中存储的数据项(可以同时缓存多个页面的数据);

d.    可以访问同一站点不同frame中的客户端脚本数据。

?缺点:

a.    有些浏览器不支持frame;

b.    源代码可以在客户端看到,有潜在的安全威胁;

c.    隐藏frame的数量没有限制,框架页面包含较多hidden frame的话,在首次加载时速度会有限制。

示例代码如下:

<frameset rows="90,*" cols="*" frameborder="NO" border="0" framespacing="0">

 <frame src="top.aspx" name="topFrame" scrolling="NO" noresize >

 <frameset rows="*" cols="13%,*" framespacing="0" frameborder="NO" border="0">

   <frame src="left.aspx" name="leftFrame" scrolling="NO" noresize>

   <frame src="main.aspx" name="mainFrame" scrolling="yes"overflow-x="hidden">

 </frameset>

</frameset>

/*frameset 标签 -- 代表HTML框架 ,当使用框架时页面中不应该有body标签

使用框架时应该声明支持框架的文档类型

属性:cols -定义水平框架,取值为象素px或者百分比%;

    rows -定义垂直框架,取值为象素px或者百分比%;*/

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("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。