Jason Shen

Any fool can write code that a computer can understand. Good programmers write code that humans can understand. ---Martin Fowler

博客园 首页 新随笔 联系 订阅 管理

1.缓存应用程序块(Caching Application Block)

1.1简介(Introduction)


     你怎样才能使你的应用程序更快呢?你可以简单地把这个问题抛给硬件,但是随着我们日益趋向于”绿色数据中心 (Green Data Center) ”,消耗更多电能及产生更多热量,不是我们的本意,也不能显示我们的环保意识(Environmental Awareness)。你当然应该总是努力写高效的代码,全力利用平台与操作系统的功能,但是,必须做什么呢?
     你能使你的应用程序更高效的方法之一是你要确保对重用的,创建时要占用很多系统资源的数据采取适当的缓存。然而,如果缓存所有数据,将适得其反。例如,我曾经安装了一个图片屏幕保护程序,它使用缓存来存储原始图像的转换版本,以便周期性显示图片集时,减少必需的处理。如果你只有几十张图片时,这种方
法很凑效,但是,如果有很多高分辨率的图片,它将很快会占用3G内存,从而使计算机(如果只有1G内存)瘫痪(bringing my machine to its knees)。
     在盲目将缓存应用于整个程序前,你应该思考:缓存什么数据,怎样缓存,缓存在哪里及什么时候缓存。下表提供了些指导:

     定义一个缓存策略(Defining a caching strategy)

问题

描述

What?

应用程序的所有用户都使用的且不会频繁变更的数据,或者,你能用来优化参考数据查找,避免网络往返及不必要的重复处理的数据。如,产品列表数据,常数,从配置文件或数据库读取的值。尽量将数据缓存为随时可用的格式。不要缓存不稳定的数据(volatile data),也不要缓存敏感数据(Sensitive Data),除非你加密它。

When?

当应用程序启动时,你就能缓存数据(你知道它是必用数据,且不可能变更)。然而,对于可能不会使用或相对不稳定的数据,只有当你的应用程序首次访问它,才应该缓存。

Where?

理想状况下,你应该将数据缓存在尽可能接近使用它的地方,对跨越物理层(physical tier)的分布式(distributed)的分层(layered)应用程序,尤为如此。例如,将用于控制用户界面(user interface)的数据缓存在表示层(presentation layer),将业务数据(business data)缓存在业务层(business layer),将用于存储过程的参数(parameters)缓存在数据层(data layer)。如果你应用程序运行在多台服务器上,且随着应用程序的运行,数据可能改变,这时常常需要使用所有服务器都可以访问的分布式缓存。

如果你缓存用于用户界面的的数据,你通常可以将其缓存在客户端。

How?

缓存是一个横切关注点(你可能在多处及许多应用程序中实现缓存)。因此,能安装在适当位置的可重用的可配置的缓存机制是必然选择。对于非分布式的缓存,缓存应用程序块是理想的解决方案。它支持内存(in-memory)缓存及后备存储器(backing store)缓存两种缓存方式,其中后备存储器缓存可以是数据库或隔离存储器(isolated storage)。这个块提供用于检索,增加及移除缓存数据的所有功能,并且支持可配置的过期(expiration)及清除(scavenging)策略。

1.2缓存块能做什么(What Does the Caching Application Block Do)?

      缓存应用程序块提供了高性能(high-performance)的,可伸缩(scalable)的缓存功能,是线程安全(thread safe)与异常安全(exception safe)的。它将数据缓存在内存,作为可选方案,它也维护一个同步的后备存储器(默认是隔离存储器或数据库)。它也提供很多过期功能,这包括针对缓存项的多种过期设置(基于时间的和基于通知的两种策略)。
      更为便利的是,如果缓存位置不适合你的需求,或者,存储或检索缓存项的机制不能完全满足你,这时,你可以修改或扩展它。例如,你可以创建你自己的自定义过期策略和后备存储器提供程序(provider),然后使用内置的扩展点将其插入应用程序块。这意味着你可以通过使用简单的API,从而在整个应用程序中实现缓存操作。
      最为重要的是,你实现的缓存可在设计时与运行时配置,这样,管理员可以在部署前后按需变更缓存行为。管理员可以变更缓存机制使用的后备存储器,为缓存内容加密以及变更清除行为(scavenging behavior)----所有这些都是通过配置文件来设置的。

1.2.1清除还是过期 (Flushed or Expired)?

     影响应用程序性能的主要因素之一是可用内存量。缓存数据虽能改进性能,但缓存太多数据也可能降低性能(如果缓存使用太多可用内存)。为了避开这种情
况,缓存块定期执行清除操作,以便当内存不足时,移除一部分缓存项。缓存项能以如下两种方式从缓存中移除:
     缓存项过期(When they expire)。如果你指定了一个过期设置,在下一次清除周期时,过期的缓存项将被从缓存中移除。你能基于绝对时间(absolute time),滑动时间(sliding time),扩展的时间格式(如,每晚午夜),文件依赖,或者从不过期来指定一组设置。你也能指定一个优先级,这样低优先级的项将被首先清除。每次清除的时间间隔及最大清除项数都是可以配置的。
     直接清除缓存项(When they are flushed)。通过调用缓存块公开的方法,你能显示地使缓存中的个别项过期(标记为移除),或者,使所有项过期。这让你可控制缓存项的可用性。这种清除机制将检测到的过期无效的项从缓存中移除。然而,清除周期开始后,你仍然不能检索保存在缓存中但标记为过期的项。
     两者的区别在于:直接清除缓存项可以移除有效的缓存项以便为使用更为频繁的项腾出空间,而缓存项过期是移除无效的过期项。需要注意的是,即使缓存项没有过期,也可以通过清除机制将其从缓存中移除。所以,当你准备检索并使用缓存项时,应该先检查其是否存在。这时,你也可以重建并缓存一个项。

1.2.2应该选择哪一种过期策略(Which Expiration Policy?)

      如果你有相对不稳定且定期更新的数据,或者,只在特定时间或时间段有效的数据,你可以使用基于时间的过期策略来确保不在有用有效生命期的项不在缓存中。你能指定如果一个项不被访问(每次被访问时,计时器实际上清零),它应该在缓存中保存多长时间,或者也可以指定绝对时间,一旦超过这个时间,不管缓存项是否被访问过,它都将从缓存中清除。
      如果你缓存的数据依赖另一个资源(如磁盘文件)的变更,你能通过基于通知的过期策略来提高缓存效率。这个缓存块包含一个过期提供程序(expiration provider),它能检测磁盘文件的变更。你也可以创建自己的自定义过期策略提供程序(expiration policy provider),用来检测诸如WMI事件,数据库事件,或者业务逻辑操作,当这些事件或操作发生时,将缓存项标记为无效。

1.3   应该何时使用缓存应用程序块(When Should I Use the Caching Application Block)

    缓存应用程序块适用于如下情形:
    1.你必须重复访问静态数据或很少改变的数据。
    2.创建,访问或传输很耗系统资源的数据访问。
    3.数据必须总是可用,即使源(如服务器)不可用。
    缓存应用程序块能应用于如下应用程序类别:
    1.Windows Forms;
    2.Windows Communication Foundation(WCF);
    3.Windows Presentation Foundation(WPF);
    4.Console application
    5.Windows service;
    6.ASP.NET Web application or Web service if you need features not included in the ASP.NET cache;

1.3.1缓存应用程序块的应用场景(Scenarios for the Caching Application Block)[略]

1.3.2缓存应用程序块的优势(Benefits of the Caching Application Block)

      当构建企业级(enterprise-scale)分布式(distributed)的应用程序时,架构师(architect)与开发人员(developer)都会面临诸多挑战。缓存应用块可以帮助他们克服其中的一些挑战,这包括:
      性能(Performance)。通过将数据存储到尽可能接近使用它们的位置,缓存块提高了应用程序的性能。这避免了数据的重复创建,处理与传输。
      可伸缩性(Scalability)。将信息存储在缓存中减少了资源的使用,增加了可伸缩性(当应用程序的需求不断增加时)。
      可用性(Availability)。通过将数据存储在本地缓存中,应用程序能恢复诸如网络延迟(network latency),Web服务问题及硬件故障等系统故障。

      此外,缓存应用块提供了一个与其它企业库应用块一致的开发模型(development model)。缓存应用块与数据访问应用块无缝集成以实现后备存储器功能。安全应用块也以相同方式包含了缓存应用块提供的缓存功能。开发人员与操作人员能使用企业库配置工具来配置缓存块。

1.3.3缓存应用块的局限(Limitations of the Caching Application Block)

    你应该将缓存应用块部署在单应用程序域(single application domain)。每个应用程序域可以有一个或多个缓存(可以是后备存储器)。缓存不能在不同的应用程序域间共享。
    虽然你能加密缓存在后备存储器中的数据,但缓存应用块不支持缓存在内存中的数据加密。如果一个恶意用户找到了一种入侵系统(compromise the system)并访问你的应用程序进程的内存的方法,他或她将能访问存储在缓存中的信息。如果这会对你的应用程序造成很大威胁(significant threat),那就不要存储诸如信用卡号或密码等敏感信息在缓存了。
    当运行缓存块的计算机内存不足(out of memory)时,缓存管理器(Cache Manager)能感知(experience)拒绝服务问题。当大量数据并发添加到缓存,添加重复数据,或者使用NotRemovable选项添加大量数据时,这种情况可能发生。在一个共享环境中,一个应用程序能导致另一个程序瘫痪。
    此外,在隔离后备存储器中的缓存项处于一个部分信任的位置,在将数据添加到隔离存储器缓存之前应加密数据。为适应特定应用程序的部署场景(scenario),应设置正确的IsolatedStoragePermission安全策略级别(level),详见MSDN上的Windows Presentation Foundation Partial Trust SecurityImproving Web Application Security: Threats and Countermeasures
    如果你缓存数据在数据库中,记住:当数据在应用程序与外部存储器之间传输时,可能遭遇篡改。所以,应该考虑将数据库安装在信任边界内,或者,使用加密存储器提供程序(encryption storage provider)加密数据。作为一种可选方案,也可以在运行缓存应用块的计算机与数据存储器之间实现传输级安全(Transport Level Security)。

1.3.4缓存应用块的替换方案(Alternatives to Using the Caching Application Block)

     在诸如有多个使用缓存的应用程序或者缓存与应用程序不在同一个系统的情形下,其它缓存解决方案可能更合适。例如,你不能在跨Web farm同步缓存。然而,如果你需要从根本上改变这个缓存块的行为,你可以用一个自定义类来替换CacheManager类。
     另一个可用选项是Microsoft Windows Server AppFabric,它为开发具有可伸缩性(scalable),可用性(available)和高性能(high-performance)的应用程序提供了一个分布式的内存应用程序缓存平台。通过使用缓存集群[cache cluster](自动管理负载均衡[load balancing]),分布式缓存使你的应用程序匹配日益增长的需求(demand)与日益增加的吞吐量(throughput)。详见:Introduciton to Caching with Windows Server AppFabric

ASP.NET Cache

    .NET Framework的System.Web命名空间包含ASP.NET缓存。ASP.NET应用程序开发人员通过System.Web.HTTPContext.Cache对象来访问这个缓存。ASP.NET缓存是针对ASP.NET应用程序来开发和优化的。然而,通过访问System.Web.HTTPRuntime.Cache对象,也可以在ASP.NET应用程序之外的程序中使用这个缓存。ASP.NET缓存需要System.Web程序集。开发人员应该核实所用的平台及环境是支持这个程序集的。

1.4   缓存应用块的设计(Design of the Caching Application Block)

为了达到如下目标,对缓存应用块进行了专门设计:

 1.提供一组大小可管理的API;
 2.在无需了解这个块的内部工作原理(internal workings)的情况下,开发人员就能将标准的缓存操作并入他们的应用程序;
 3.只需使用企业库配置工具作简单配置;
 4.高效地执行;
 5.线程安全的(thread safe)。当从多线程程序中调用这个应用块时,若没有产生与这些线程的无用交互,则认为代码是线程安全的。
 6.在访问这个应用块时,如果发生异常,应保证后备存储器完整无缺(intact)。
 7.确保内存缓存与后备存储器的状态(state)保持同步;

1.4.1设计亮点(Design Highlights)

      
      缓存应用块包含CacheManager类,它是ICacheManager接口的默认实现。当你实例化默认CacheManager类的实例时,它在内部创建一个Cache对象。创建Cache对象后,在后备存储器中的所有数据加载到Cache对象表示的内存中。然后,应用程序能对默认的CacheManager对象发出请求,如检索缓存数据,添加数据到缓存,从缓存删除数据。
      当应用程序使用GetData方法向CacheManager对象发出请求来检索一个缓存项时,CacheManager对象转发(forward)这个请求给Cache对象。如果这个项在缓存中,将返回给应用程序。如果不在缓存中,则返回null。如果项过期,也将返回null。
      当应用程序使用Add方法向CacheManager对象发出请求以添加项到缓存时,CacheManager对象也转发(forward)这个请求给Cache对象。如果已有一个相同键的项在缓存中,则Cache对象首先删除它,然后添加新的项到内存和后备存储器。如果后备存储器是默认的后备存储器(NullBackingStore),则只写到内存。如果添加的缓存项的数量超过了预先设定的值,则BackgroundScheduler对象就开始进行清除(scavenging)工作。当添加一个缓存项时,应用程序能使用Add方法的重载来指定一组过期策略,清除优先级(scavenging priority),和一个实现了IcacheItemRefreshAction接口的对象。可以使用这个对象来刷新过期项。
      当添加一个尚不在内存哈希表(in-memeory hash table)的项时,Cache对象首先创建一个虚拟缓存(dummy cache)项并添加到内存哈希表。然后在内存哈希表中锁定这个虚拟缓存项,并添加到后备存储器,最后用新的缓存项替换内存哈希表中的现有缓存项。当向后备存储器中写数据时,如果发生了异常,Cache对象就移除添加到内存哈希表中的虚拟项(dummy item),然后终止执行。缓存应用块提供了一个强异常安全保障。这意味着如果Add操作失败,缓存状态会回滚到添加项之前的状态。换言之,要么操作完全成功,要么缓存状态保持不变(对于Remove与Flush方法而言,也是这样)。
      BackgroundScheduler对象会定期监视缓存中项的生命期。如果一个项过期,BackgroundScheduler对象首先移除它,然后,通知应用程序这个项已经移除。此时,是否刷新缓存是应用程序的责任(responsibility)了。

1.4.2设计细节(Design Details)[略]

1.4.3过期过程设计(Design of the Expiration Process)

      缓存块的过期过程是由BackgroundScheduler来完成的。它定期检查哈希表中的缓存项,以确定是否有过期项。你使用配置工具配置IcacheManager接口的默认实现CacheManager的实例时,你可以控制过期周期。

      缓存块提供的过期策略有如下几种:

      1. 绝对(Absolute)这意味着项在特定时间会过期。

      2. 滑动(Sliding)这意味着从项最后一次被访问到一个指定的时间过去后,缓存项就过期。默认的时间是2分钟。

      3. 扩展的格式(Extended format)这允许你指定非常详细的过期条件。例如,你可以指定每周六晚上10:03过期,或者,每月的第三个星期二过期。
          扩展格式列在ExtendedFormat.cs文件中。
      4. 文件依赖(File dependency)这意味着指定的文件被修改后,缓存项过期。
      5. 从不过期(Never expired)这意味着缓存项从来不可能过期,虽然,这个应用块如果检测到内存不足,会移除部分缓存项。

      头三个策略(absolute,sliding,extended format)称为基于时间的过期。对于不稳定的缓存项(如会定期刷新或者只在特定时间有效的数据),你应该使用基于时间的过期策略。基于时间的过期让你设置只要数据是活动的,它就能保存在缓存中。例如,如果你正在写一个程序,它通过从频繁更新的网站获取的数据来跟踪外汇汇率,那么当汇率在源网站中显示为一个静态值时,你将其缓存起来。在这种情况下,你可以基于源网站更新的频率来设置一个过期策略。
      第四个策略(file dependency)称为基于通知的过期。它基于特殊文件来定义缓存项的有效性。如果文件被修改,缓存项就失效并被从缓存中移除。
      Add方法有两个重载。一个假定使用默认的过期策略(从不过期)。另一个让你自己设置过期策略。你能使用任意多个策略(包括你自己创建的策略)。如果一个缓存项应用了多个策略,只要有一个策略满足过期标准,则这个缓存项就会过期。

标记与清除(Marking and Sweeping)

     过期是一个包含两个步骤的过程。第一步称为标记;第二步称为清除。这个过程被分成几个独立的任务来避免可能发生的冲突(如果应用程序将要使用一个缓存项,而刚好BackgroundScheduler正准备使之过期。)
     在标记期间,BackgroundScheduler生成哈希表的一个复本并检查其中的每个缓存项看是否有过期的。在这个过程中,它锁定每一项。如果一个项符合过期条件,BackgroundScheduler会在此项上设置一个标志(flag)。
     在清除期间,BackgroundScheduler重新检查每个标记的缓存项看是否自标记以来,是否有被访问过。如果有被访问过,则此项继续留在缓存中,如果没被访问过,则使之过期并从缓存中移除掉。

回调(Callbacks)

     作为可选方案,开发人员能使用Add方法的一个重载,这样当一个项过期并被移除后,应用程序会收到一个回调。如果有必要,应用程序能刷新那个项。
     当应用程序退出后,缓存项还能保存在缓存中,并且当应用程序重启时,那些项可能已经过期了。在这种情况下,缓存项保存在缓存中,当执行第一个过期周期时,针对那些项的回调就会发生。然而,在第一个过期周期执行前,如果应用程序请求了一个过期项,缓存会执行一个回调,并返回null给应用程序。这样就确保了每个过期项都会有回调发生,从而阻止应用程序接收已经过期的项。

1.4.4清除过程设计(Design of Scavenging Process)

      缓存块的清除过程是由BackgroundScheduler对象来完成的。每次添加一个项它都会检查缓存看缓存中的项数是否已达到预先设定的值。你配置缓存管理器的一个实例时,可以使用配置工具来设置这个值。你也可以设置当清除开始时,从缓存中清除多少项。
      当添加一个缓存项时,代码可以设置四个优先级(Low ,Normal , High ,NotRemovable)中的一个。通过基于优先级执行一个主排序和基于最后一次访问的时间执行一个次排序,BackgroundScheduler对象决定清除哪个缓存项。例如,与一个三年来从未被访问过的优先级为High的缓存项相比,刚被访问过的优先级为Low的缓存项将被先清除。默认值是Normal。
      如果你想一个项到过期时才被清除,那么设置其优先级为NotRemovable。然而,缓存不应该作为数据存储的唯一位置。缓存是用来提高性能的,而不是作为永久存储器。
      与过期过程不同,清除过程一次执行完标记与清除。

posted on 2010-12-05 22:19  Jason Shen  阅读(1041)  评论(1编辑  收藏  举报