热缓存 IHotCache --ESBasic 可复用的.NET类库(19)

1.缘起:

    假设我们有一个订单系统,现在这个系统要增加一个功能――允许客人查核他认为有问题的订单的详细信息。当客人觉得自己的某个订单不对劲时,他首先会从订单系统查询这个订单的详细信息,然后打电话告诉我们的客服有问题的订单的编号,客服再去查核,如果属实,客服还要进一步上报,如果该订单非常重要,则可能需要更进一步上报复查等。

    从这个需求我们看到,同一个订单可能会在比较短的时间内查询数次甚至数十次,所以我们可以称这个订单为“热点”订单。而其它的成千上万的订单可能在一个月内都不被查询一次。

    我设计热缓存ESBasic.ObjectManagement.Cache. IHotCache就是缓存类似“热点”订单的这些对象。

热缓存的形象示意图如下:

 

 

2.适用场合:

根据上述的描述,我们可以总结一下热缓存IHotCache的使用场合:

(1)根据我们系统的需求,目标对象群中的某些对象的“热”的程度可以明显地与其它对象区分开来。

(2)加载某一类型的对象到我们的系统中需要花费比较大的气力(比如加载速度很慢、加载过程很繁琐),而且一旦加载就会被经常使用。

(3)在指定的时间周期内不被访问的对象可以被卸载掉。如果一个“热”对象变“冷”了,那么我们就可以将其从热缓存中移除。

(4)当然,要被缓存的对象必须有唯一的ID。

 

3.设计思想与实现

IHotCache的接口定义如下:

    /// <summary>
    /// IHotCache 用于缓存那些活跃的对象,并定时删除不活跃的对象。该接口的实现必须是线程安全的。
    /// </summary>    
    public interface IHotCache<TKey, TObject> where TObject : class
    {
        /// <summary>
        /// DetectSpanInSecs 多长时间检测一次对象是否活跃,单位:秒。
        /// </summary>
        int DetectSpanInSecs { set; }

        /// <summary>
        /// MaxMuteSpanInMinutes 对象最大的沉默时间(分钟)。如果一个对象在MaxMuteSpanInMinutes时间间隔内都不被访问,则将被从缓存中清除。
        /// 如果该属性的值被设置为小于或等于0,则表示永远不会从缓存中清除。
        /// </summary>
        int MaxMuteSpanInMinutes { set; }

        /// <summary>
        /// MaxCachedCount 最多缓存的对象个数。当超过此个数时,不再缓存新的对象。
        /// </summary>
        int MaxCachedCount { get; set; }

        IObjectRetriever<TKey, TObject> ObjectRetriever { set; }
        int Count { get; }
        long RequestCount { get; } //请求次数
        long HitCount { get; }//命中的次数
        DateTime LastReadTime { get; }        

        void Initialize();
        void Clear();
        void Add(TKey id, TObject obj);
        void Remove(TKey id);
        
        /// <summary>
        /// Get 如果缓存中存在目标则直接返回,否则通过ObjectRetriever提取对象并缓存。
        /// </summary>      
        TObject Get(TKey id);

        IList<TObject> GetAll();

        event CbSimple CacheContentChanged;
    }

 

注意这个接口的泛型参数TObject有一个泛型约束,其表明TObject必须是一个引用类型,这表示我们不能使用热缓存来缓存那些值类型的对象。

当你向IHotCache请求一个对象时,如果对象不在热缓存中(有可能是从来还未加载进缓存,也有可能是因为对象变“冷”后被移除缓存了),则会加载该对象到热缓存中并返回。我们是借助前面介绍的对象获取器IObjectRetriever来获取目标对象的。

    热缓存会每隔一段时间检测一次缓存中的对象是否已经变“冷”――即在MaxMuteSpanInMinutes时间段内都没有被访问过。这个定时检测就是使用循环引擎AgileCycleEngine来做到的。如果一个对象已经变“冷”,则会将其移除热缓存――热缓存只缓存那些“热”的东西,所以放“冷”了的就必须把它移除掉。

      RequestCount和HitCount分别表示热缓存接收到的请求对象的次数,和缓存命中的次数――即无需IObjectRetriever加载而直接从缓存中返回对象的次数。这两个属性提供一些统计数据,以反映我们当前使用热缓存的价值究竟有多大,以此我们可以判断这个地方是否真的需要使用热缓存。

    关于HotCache的具体实现,还需要注意以下几个方面:

(1)为了允许在多线程的环境中使用热缓存,所以HotCache必须在对内部的dictionary操作的时候进行加锁控制。

(2)热缓存在初始化(Initialize方法)时,启动循环引擎来定时检测缓存的每个对象的冷热程度。

(3)使用CachePackage类来封装每个被缓存的对象,其中主要是记录了被封装对象的最后一次访问时间。

(4)如果缓存中不存在目标对象、IObjectRetriever也加载不到目标对象,Get方法将返回null。

 

4. 使用时的注意事项

(1)有两种方式可以将对象添加到热缓存中,一种是热缓存借助IObjectRetriever自动加载对象;另一种是通过调用其Add方法加入。如果我们已经预计到某个对象是“热”对象,那么就可以先将其Add到热缓存中以备用。特别是,当IObjectRetriever获取这个对象比较费气力时:)。

(2)热缓存加速了对“热”对象的访问,但是这是以占用内存为代价的。如果你的对象访问比较平均,没有凸显出特别“热”的对象,那么使用热缓存的作用就不大了。

(3)当你确定不会再使用某个对象时,可以立即调用Remove方法将其从热缓存中手动移除,而不是等到其变“冷”后才被热缓存自动移除。当这样的对象比较多,而且对象比较大时,这样做可以立即释放比较大的内存空间。

(4)如果某个对象已经被判定为无效或者已被外界修改(缓存中的对象的数据已经是老版本),则可以调用Remove方法将其从热缓存中移除。

(5)MaxMuteSpanInMinutes属性设置为多少比较合适,取决于你的具体应用,也许是10分钟,也有可能是24小时。如果这个属性和DetectSpanInSecs属性、MaxCachedCount属性能进行合理搭配设置,则可以使得热缓存的运行价值最大化。

 

5.扩展

热缓存IHotCache暂时没有任何扩展。

 

 

注: ESBasic已经开源,点击这里下载源码。
    ESBasic开源前言

 

 

时间: 2024-10-11 11:19:25

热缓存 IHotCache --ESBasic 可复用的.NET类库(19)的相关文章

智能字典缓存 ISmartDictionaryCache-- ESBasic 可复用的.NET类库(18)

1.缘起:    假设我们有一个会员管理系统,需要向各方提供查询会员基础资料的功能.会员一经注册,其基础资料就将不再发生变化(如会员帐号.身份证ID.注册时间等等).    基于这样的需求,我们可以将会员的基础资料"永久地"缓存在内存中,从而提升对任何一个会员基础资料的查询速度.    我设计了ESBasic.ObjectManagement.Cache.ISmartDictionaryCache来对这种性质的对象进行缓存.     职能字典缓存的形象示意图如下:   2.适用场合:

Round缓存管理器RoundCacheManager--ESBasic 可复用的.NET类库(26)

1.缘起:     在增量自动获取器章节的缘起部分,我们曾提到增量缓存,本节我们将深入探讨它以及用于管理增量缓存的管理器.我们还是以增量自动获取器章节提到的例子作为基础,并做更进一步的讨论.       OK,现在让我们开始这有趣的旅程. 首先,基于前面例子给出的上下文,我们知道IIncreaseAutoRetriever获取的增量是用于累积当天的已成交订单报表的."当天已成交报表"就是一个典型的增量缓存,每当有新的增量到来,都会累加到上面. 我们假设今天是2009.07.08,那么我

层级结构缓存IHiberarchyCache -- ESBasic 可复用的.NET类库(24)

1.缘起:     从IMultiTree到IAgileMultiTree,一切进展得都不错.但是,还有改进的地方.多叉树的一个优点在于,根据指定的节点能够非常迅速地找到其所有的子节点.但是缺点在于,根据节点值的ID定位到目标节点不够快,因为需要对所有的节点进行遍历操作.当节点非常多.层次非常深时,这种定位操作可能会严重的影响效率.     我设计了层级结构缓存ESBasic.ObjectManagement.Cache.IHiberarchyCache来加速这种根据节点值ID定位节点的访问.所

定时刷新缓存管理器 IRefreshableCacheManager --ESBasic 可复用的.NET类库(16)

1.缘起:     为了提升系统的性能或减轻数据库的压力等原因,我们经常在系统中使用缓存来把那些经常使用的数据保留在内存中.如果因为某些原因,缓存中这些经常使用的数据不能及时与数据源进行同步更新,那么采用定时刷新缓存中的数据有可能就是一种合适的选择.     如果你的缓存是定时刷新,那么你就需要自己为其维护一个定时器或循环引擎.如果你的系统中像这样定时刷新的缓存有多个,而且每个缓存定时刷新的时间间隔又要求不一样,那么,使这些缓存按照你预想的情况进行运转,你就需要花费一些气力.     我设计了定

ESBasic 可复用的.NET类库(00) -- 开源前言(附下载)

自从03年正式使用.NET开发以来,已经走过了6个年头,这期间我积累了几套类库和框架,ESBasic便是其中最基础的一个类库.ESBasic是Enterprise Service Basic的缩写,虽然也简写为ESB,但是它和Enterprise Service Bus(企业服务总线)没有任何关系.ESBasic是我能够快速和高效开发应用程序的利器之一,开这个专门的blog是想将它介绍给大家,希望能对大家有所启发. ESBasic覆盖的内容包括:对象管理.插件.网络(Socket).多线程.Em

双向映射 IBidirectionalMapping -- ESBasic 可复用的.NET类库(11)

1.缘起:     假设我们的用户管理系统要求用户的ID和Name都必须是唯一的,并且用户的ID和Name一经确定就不能被修改.而且管理系统经常需要根据ID来查找Name,也经常需要根据Name来查找ID.根据这样的需求,我们可以考虑使用一个Dictionary来将ID和Name缓存起来,通常ID作为Key,Name作为Value.这样便可实现通过ID查询Name的快速查找,但是,通过Name查找ID就不是那么快了,因为涉及到对Dictionary的Values做遍历的操作.那么,有可能使得通过

对象池 IObjectPool -- ESBasic 可复用的.NET类库(15)

1.缘起:     对象池应该是一个"历史悠久"的概念了,像我们经常说的线程池.还有ADO.NET中的数据库连接池等,都属于对象池的应用.     我们的应用有时也会碰到需要使用对象池的情况,我举个例子说明一下.假设,我们需要记录某个类MyClass的每个方法每次被调用时方法执行所消耗的时间,而且,这个类是使用在多线程的环境中的,每个方法都可以同时在多个线程中执行,不需要被同步,这样可以使并发达到最大.     好,我们可以使用Stopwatch这个类来准确地记录每个方法的时间,关键是

多叉树 IMultiTree -- ESBasic 可复用的.NET类库(22)

1.缘起:     假设我们要描述一个集团公司的组织结构,这个集团公司的体系分为如下几层:集团.公司.子公司.部门.小组.即一个集团由多个公司构成,每个公司又有几个子公司构成,每个子公司拥有多个部门,每个部门又内分为几个小组.     很明显,这种体系结构就是一个多叉树.我设计了ESBasic.ObjectManagement.Trees.Multiple.IMultiTree来抽象多叉树,其提供了很多简便的方法让我们对多叉树进行节点查询和操作. 多叉树的形象示意图如下: 从上图中我们看到,集团

优先级管理器 IPriorityManager -- ESBasic 可复用的.NET类库(14)

1.缘起:     假设我们的订单处理系统所要处理的订单是有优先级的,也就是说,不同的订单类型所要求被处理的紧迫程度不同,对那些优先级高的注单要先处理,对于优先级低的注单可稍后处理.对于处于同一优先级的订单了,就按照其到达的先后顺序进行处理.     这是一个典型的管理具有优先级的对象的需求,注单就是具有优先级(With Priority)的对象.我设计了ESBasic.ObjectManagement.Managers.IPriorityManager优先级管理器(确切地说,应该称之为"具有优