缓存应用程序为以下目的而设计:
- 提供一个大小可管理的 API 集合。
- 允许开发人员添加标准的缓存操作到他们的应用程序中,而不用学习应用程序块的内部工作。
- 用 Enterprise Library 配置控制台来简化配置。
- 有效率的执行。
- 线程安全。某些东西在被多个程序线程调用而没有属于那些线程的不必要的交互时,它被视为是线程安全的。
- 如果在访问后端存储时发生异常,确保后端存储依然是完整的。
- 保存内存缓存的状态与后端存储保持同步。
设计亮点
图 1 说明了缓存应用程序块中关键类的相互关系。
当使用 CacheFactory 初始化一个 CacheManager 的实例时,它在内部创建了一个 CacheManagerFactory 对象,然后创建一个Cache 对象。在 Cache 对象被创建后,所有在后端存储中的数据被加载到一个包含中 Cahce 对象的内存表示中。然后应用程序就可以向 CacheManager 对象发出请求以获取缓存的数据、添加数据到缓存以及从缓存中移除数据。
当应用程序使用 Add 方法发送一个请求到 CacheManager 对象以添加条目到缓存中时,CacheManager 对象又将请求发送给 Cache 对象。如果已存在同样键的的条目,Cache 对象会在添加新条目到内存缓存和后端存储之前删除它。如果后端存储是默认的 NullBackingStore ,数据将只是写到内存中。如果在条目添加时已缓存条目的数量已超出了预先设置的限制,BackgroundScheduler 对象将开始清理。在添加条目时,应用程序可以使用 Add 方法的一个重载来指定一个过期策略数组、清理优先级,以及一个实现了 ICacheItemRefreshAction 接口的对象。
当添加的条目没有在内存哈希表中时,Cache 对象首先创建一个模型缓存条目并将它添加到内存哈希表中。然后锁定内存哈希表中的条目,添加条目到后端存储中,最后用新的缓存条目替换掉在内存哈希表中的条目。(在条目已存在于内存哈希表中的情况下,它替换模型条目。)如果在写入后端存储时发生了异常,它会移除添加到内存哈希表中的模型条目且不再继续。缓存应用程序块强制了一个强壮的异常安全保证。这意味着,如果 Add 操作失败,缓存的状态将回滚到尝试添加条目以前的状态。换句话说,操作要么完全成功,要么缓存的状态保持不变。(这也同样适用于 Remove 和 Flush 方法。)
BackgroundScheduler 对象周期性的监视缓存中条目的生命期。当条目过期时,BackgroundScheduler 对象首先移除它,然后可能通知应用程序条目已被移除。此时,应用程序的响应时刷新缓存。
详细设计
CacheManager 类是缓存应用程序块其余部分和应用程序之间的接口,所有的操作都通过此类。对于使用没有修改过的应用程序块的开发人员,CacheManager 对象提供了所有添加、获取和从缓存中移除条目的所需方法。通过 CacheManager 对象调用的所有方法都是线程安全的。
要创建 CacheManager 对象实例,应用程序要使用 CacheFactory 类,然后使用 CacheManagerFactory 类。CacheManagerFactory 类创建所有实现 CacheManager 所需要的内部类。
每个名称只能用于一个缓存,要创建多个缓存的实例,就得使用多个名称。要注意的是,不同的缓存,也就是不同名称的缓存,不能共享同样的后端存储。每个 CacheManager 对象也只有一个后端存储。
Cache 对象接收来自 CacheManager 对象的请求,并实现所有缓存数据的后端存储和内存表示之间的操作。它包含一个保存数据内存表示的哈希表。( 这是用户看到的格式。)一个数据条目被包装成一个 CacheItem 对象,此对象包含了数据本身,以及如条目的键、优先级、RefreshAction 对象和过期策略(或策略数组)等其他信息。它被存储在哈希表中。Cache 对象还使用一个同步的哈希表来控制应用程序和 BackgroundScheduler 对缓存中条目的访问。Cache 对象为整个缓存应用程序块提供了线程安全。
BackgroundScheduler 对象的职责是终止过期的缓存条目和清理低优先级的缓存条目。一个 PollTimer 对象会触发过期周期,以及一个数量限制会触发清理进程,这些都在配置文件中进行设置。
BackgroundScheduler 对象是主动对象模式( Active Object Pattern )的一个实现。这意味着与 BackgroundScheduler 对象会话的其他对象(在此是 PollTimer )就像已存在于调用对象的线程中。在它被调用后,BackgroundScheduler 将请求打包成一条消息,并将它放到一个队列集合对象中,而不是马上执行所请求的行为。(记住,这都发生在调用者的线程中。)这个队列是生产者/消费者(Produceer-Consumer)模式的一个示例。当BackgoundScheduler 处理完消息时,一个内部线程将从队列中取出消息。实际上,BackgoundScheduler 串行化了所有清理和过期请求。
对于它自己的线程,BackgroundScheduler 对象按顺序从队列中移除消息,然后执行请求。对于过期过程,它调用ExpirationTimeoutExpiredMsg 类的 Run 方法。对于清理过程,它调用 StartScavengingMsg 类的 Run 方法。在一个单一线程中顺序执行操作的好处是保证代码运行在单一线程的环境中,这使代码和它的影响更容易理解。
包含在缓存应用程序块中的缓存存储类是 DataBackingStore 类、IsolatedStorageBackingStore 类和 NullBackingStore 类。如果有兴趣开始自己的后端存储,你的类必须实现 IBackingStore 接口,或者从实现了 IBackingStore 接口的抽象类 BaseBackingStore 继承。此类包含了普通策略的实现和可以用于所有后端存储的实用方法。
DataBackingStore 类在后端存储是数据访问应用程序块时被使用。用配置控制台配置它使用一个命名的数据库实例。IsolatedSorageBackingStore 类在特定域隔离的存储中存储缓存条目。用配置控制台可以配置它使用一个命名的独立存储。缓存应用程序块通过 IBackingStore 接口与所有的后端存储隔离。
DataBackingStore 和 IsolatedStorageBackingStore 类可以在持久存储前加密缓存条目数据。缓存条目数据的加密可以通过配置使其可用。使用配置控制台,缓存存储可以配置为使用命名的对象加密算法提供程序。命名的提供程序也可以在用条目数据组装缓存之前从缓存存储中读取数据,解密数据时使用。
过期处理的设计
缓存应用程序块的过期处理由 BackgroundScheduler 来执行。它周期性的检查哈希表中的 CacheItem 看是否有条目已过期。在使用配置控制台配置一个 CacheManager 实例时可以控制过期周期发生的频率。
缓存应用程序块提供了四个过期策略:
· 绝对时间(Absolute)。这意味着条目在特定的时间过期。
· 滑动时间(Sliding)。在此的意思是在从它最后一次被访问后经过了指定时间后过期。默认的时间是 2 分钟。
· 扩展格式。这允许开发人员更细致的处理条目何时过期。例如,可以指定条目在每个星期六的晚上 10:03 分过期,或者在一个月的第三个星期二过期。扩展格式都列出在 ExtendedFormat.cs 文件中。
· 文件依赖。条目在特定文件被修改后过期。
前面三个策略,绝对时间、滑动时间和扩展格式都可认为是基于时间的过期。可以将基于时间的过期用于短暂的缓存条目,例如那些定期刷新或仅在指定时间有效的条目。基于时间的过期让你设置仅在缓存中保持最新的条目的策略。例如,如果编写了一个跟踪当前汇率的的应用程序,汇率数据从一个频率更新的 Web 站点上获取,就可以缓存当前汇率为那些汇率在源 Web 站点上保持不变的时间。在这种情况下,将设置基于 Web 站点更新频率的过期策略。
第四种策略,文件依赖,可以认为是一种基于通知的过期。它定义了缓存的条目的有效性基于一个特定的文件。如果文件被修改,缓存的条目就不再有效并从缓存中移除。
Add 方法有二个重载。NeverExpired 接受默认的过期策略,另一个重载允许自己设置过期策略。可以使用你能想到的所有策略,包括自己创建的策略。(关于用添加自己的过期策略来扩展缓存应用程序的更多详细信息,请参见添加新的过期策略。)如果有一个有多个策略的条目,条目将在最严格的策略到来时过期。
标记和清除
过期是一个两部分的过程。第一部分可以认为是标记,第二部分可以认为是清除。处理分成隔离的任务是为了避免如果应用程序使用 BackgroundScheduler 试图过期的条目可能发生的冲突。
在标记期间,BackgroundScheduler 标记哈希表的一个副本,并检查其中的每个缓存条目看它是否可以被过期。在它这样做时,它锁定了条目。如果条目可以过期,BackgroundScheduler 给在缓存中条目设置一个标记。
在清除期间,BackgroundScheduler 重新检查每个标记的 CacheItem ,看它在标记后是否被访问过。如果它被访问过话,条目将保持在缓存中。如果它没有被访问,它将被过期并从缓存中移除。在条目过期时会触发一个 Windows Management Instrumentation( WMI )事件。
回调
可选择的是,开发人员可以使用 Add 方法的一个重载来指定应用程序在条目过期并从缓存中移除后接收一个回调。如果需要,应用程序将刷新缓存。
条目也许可以在应用程序退出时依在缓存中,并且可能在应用程序重启时其中许多已过期。在这种情况下,条目保持在缓存中,并且条目的回调发生在第一个过期周期期间。然而,如果应用程序在第一个过期周期发生前请求一个过期的条目,缓存将执行回调,并返回 null 给应用程序。这确保每个过期条目回调的发生,并防止应用程序接收到一个过期的条目。
清理处理设计
缓存应用程序块的清理处理由 BackgroundScheduler 对象执行。它在每次添加条目时检查缓存,看缓存中条目的数量是否已到了预定的限制。可以在使用配置控制台配置一个缓存管理器实例时设置这个限制,也可以设置在清理开始后要从缓存中移除多少个条目。
在条目添加到缓存时,它可以被给予这四个优先级之一:Low, Normal, High 或者 Not Removable 。BackgroundScheduler 对象用基于优先级的主排序和基于条目最后访问时间的次排序来决定哪个条目将被删除。例如,才被使用过的 Low 优先级的条目将在已三年没有访问过的 High 优先级之前被清理。默认值是 Normal 。
NotRemoveable 优先级被用在当要条目保持在缓存中直到它到期时。然而,缓存不能仅使用为数据条目已存在的位置。缓存将用于提高性能,不使用为永久存储的形式。
不像过期处理,清理处理在单一过程中执行标记和清除。关于标记和清除的更多信息过期处理设计。
本文来自云栖社区合作伙伴“doNET跨平台”,了解相关信息可以关注“opendotnet”微信公众号