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

1.缘起:

    在增量自动获取器章节的缘起部分,我们曾提到增量缓存,本节我们将深入探讨它以及用于管理增量缓存的管理器。我们还是以增量自动获取器章节提到的例子作为基础,并做更进一步的讨论。

      OK,现在让我们开始这有趣的旅程。

首先,基于前面例子给出的上下文,我们知道IIncreaseAutoRetriever获取的增量是用于累积当天的已成交订单报表的。“当天已成交报表”就是一个典型的增量缓存,每当有新的增量到来,都会累加到上面。

我们假设今天是2009.07.08,那么我们可以将当前的增量缓存的ID标记为20090708,每隔一分钟都会有新的增量累加到该增量缓存实例中去。直到时钟指向2009.07.09的00:00:00时,表示当前这一天已经结束,此时,将创建一个新的ID为20090709的增量缓存实例来容纳新的增量。而ID为20090708的增量缓存实例将不会再有新的增量进来,所以,其内容也将不再发生变化。此时,我们可以将ID为20090708的增量缓存实例序列化后进行存储。这样做的好处是,当以后任何时候我们需要查询2009.07.08这一天的报表时,可以直接从存储设备加载20090708的增量缓存到内存中并反序列化,然后返回结果,这样就不用再去重新统计这一天的所有已成交的订单了。

所以,当时间进入2009.07.09,那么ID为20090708的增量缓存实例就变成了一个历史缓存,而标记为20090709的新缓存会处理新来的增量。如此,我们可以使用一个管理器将所有的历史缓存和当前正在运行的缓存管理起来。我设计的ESBasic.ObjectManagement.Increasing.Management.RoundCacheManager就是为的这个目的。

  Round缓存管理器的形象示意图如下:

  

 

2.适用场合:

       RoundCacheManager是依赖于IIncreaseAutoRetriever的,所以如果要使用RoundCacheManager,首先必须满足IIncreaseAutoRetriever所需的条件,另外还有RoundCacheManager需要的额外增强的条件。

(1)需要定时从数据源获取新的增量数据。

(2)数据源可能不只一个。

(3)需要支持“轮”(Round)的概念。在Round切换时,需要能够准确识别增量断点。

(4)增量数据有某个字段是递增的。

(5)需要将获取的增量数据放入到正确的增量缓存。

(6)在Round发生切换时,需要产生新的增量缓存来接收新的增量。而历史的增量缓存也需要被管理起来。

(7)被管理的历史缓存在其过期的时候,会自动从管理器中移除。

 

3.设计思想与实现

在分析RoundCacheManager的源码之前,我们先将其会涉及到的一些重要概念说明一下。

    由于增量缓存是基于Round的,比如上面提到的一天为一个Round,所以,在ESBasic中称这样的缓存为Round Cache,如果是当前正在使用的增量缓存,则称为Round Increasing Cache。

    当Round的切换点到来时,当前的Round Increasing Cache就会演变成一个属于历史的Round Cache。    

首先,ESBasic要求Round Cache必须实现IRoundCache泛型接口,IRoundCache接口定义如下:

    /// <summary>
    /// IRoundCache 某一Round的完整的数据缓存。用于被序列化存储。
    /// </summary>
    public interface IRoundCache<TRoundID>
    {
        TRoundID RoundID { get; }
    }

 

该接口的泛型参数TRoundID用于抽象Round ID的类型,比如我们前面例子中提到的20090708,其TRoundID就是一个整数类型。

IRoundCache接口只有一个RoundID属性,用于返回该Round Cache的ID。

    一般来说,我在实现IRoundCache接口时,会将实现类标记为可序列化的,这是为我们前面提到的“可以将历史的Round Cache序列化后进行存储”做准备的。

    当前正在使用的Round Cache必须实现IRoundIncreasingCache泛型接口,其定义的源码如下:

    /// <summary>
    /// IRoundIncreasingCache 用于存储当前Round数据的增量缓存
    /// </summary>
    public interface IRoundIncreasingCache<TRoundID, TRoundCache, TObject> where TRoundCache : IRoundCache<TRoundID>
    {
        TRoundID RoundID { get; }

        void AddIncreasement(IList<TObject> list);

        /// <summary>
        /// CreateRoundCache 当需要序列化存储当前Cache时,先转化为轻量的TRoundCache对象,然后再进行存储。
        /// </summary>        
        TRoundCache CreateRoundCache();
    }

 

    该接口有三个泛型参数:TRoundID、TRoundCache、 TObject。其中TRoundID和 TObject我们已经很熟悉了,而TRoundCache就是实现了IRoundCache接口的类型。

      IRoundIncreasingCache接口的RoundID属性与IRoundCache接口的同名属性是一样的含义,这表明当前正在使用的增量缓存也有自己的唯一ID。

      AddIncreasement方法用于将获取到的增量数据累加到IRoundIncreasingCache中。

      CreateRoundCache方法通常在Round切换点出现并完成当前Round的最后一次增量累积时,用于将IRoundIncreasingCache转化为一个历史的Round Cache,这样便可将其序列化存储。 

    从存储设备加载历史的Round Cache以及将Round Cache序列化存储的工作是由IRoundCachePersister接口来完成的,其定义如下:

    /// <summary>
    /// IRoundCachePersister 用于持久化或加载RoundCache。
    /// </summary>   
    public interface IRoundCachePersister<TRoundID, TRoundCache> where TRoundCache : IRoundCache<TRoundID>
    {
        /// <summary>
        /// Persist 注意,该方法不得抛出异常。
        /// </summary>       
        void Persist(TRoundCache roundCache);

        void Delete(TRoundID roundID);

        IDictionary<TRoundID, TRoundCache> LoadCaches(int maxHistoryCountInMemory);
    }

   该接口的Persist方法用于将一个Round Cache持久化到存储设备中;Delete方法用于从存储设备删除指定ID 的Round Cache记录;LoadCaches用于从存储设备加载离当前最近的N个Round Cache。 

 

接下来,我们将注意力转移到本节的主角RoundCacheManager上来,RoundCacheManager的工作职责包含以下几个方面:

(1)初始化时,RoundCacheManager会做以下几个动作:首先,从存储设备加载一定数量的历史Round Cache到内存中并管理起来。其次,创建一个属于当前的Round Increasing Cache来处理即将到来的增量数据。

(2)当Round切换点到来时,将当前的Round Increasing Cache转化为一个历史的Round Cache,并将其序列化存储。然后创建一个新的Round Increasing Cache来处理后续的增量数据。

(3)如果管理器中的某些历史Round Cache不再被需要,则会从管理器中移除。

 

RoundCacheManager的类图如下所示:   

       

  我将RoundCacheManager的实现要点罗列如下:

(1)CurrentRoundCache只读属性用于暴露当前正在工作的增量缓存。

(2)LastRefreshTime属性记录了最后一次获取增量并完成增量累积的时间。

(3)MaxHistoryCountInMemory用于指示在内存中最多保存多少个历史的Round Cache。在Round切换点时,将会产生新的历史Round Cache,这时如果历史Round Cache的数量超过MaxHistoryCountInMemory,则管理器会从内存中移除最老的那个Round Cache。

(4)当Round切换点发生时,管理器将触发NewRoundStarted事件以通知相关预订者。

(5)AddRoundCache方法和RemoveRoundCache方法用于操作管理器中的历史Round Cache。这两个方法都有一个bool参数,用于指示是否要针对存储设备做相应的动作。

(6)GetHistoryRoundCache用于返回管理器中存在的某个历史Round Cache。

   如果要获取当前正在工作的Round Cache,应该直接使用CurrentRoundCache属性。如果目标Round Cache在管理器中不存在,GetHistoryRoundCache将返回null。为避免返回null,可以先调用ContainsHistory以查明目标是否存在于管理器中。

 

       RoundCacheManager是一个abstract类,其有三个abstract方法留给派生类去实现。这三个方法的实现取决于具体应用的需求。

       CreateNewRoundIncreasingCache方法用于创建一个当前正使用的增量缓存。该方法之所以要设计为abstract,是因为在RoundCacheManager中,我们并不知道该如何创建一个TRoundIncreasingCache对象,它有哪些构造函数、需要些什么参数等等,我们对此一无所知。

       GetNextRoundID方法用于根据当前的Round ID创建下一个Round ID。这也是由具体应用来决定的。

       GetExpiredHistoryList方法会在每次管理器中的历史Round Cache的数量发生增加时被调用,RoundCacheManager会依据该方法返回的列表来移除那些过期的历史Round Cache。

 

4. 使用时的注意事项

(1)     RoundCacheManager以基类被继承的方式提供复用。RoundCacheManager搭建起了一个关于增量数据的获取、增量缓存的累积、Round的自动切换、增量缓存的自动持久化这样一个流程的完整骨架。使用者只需要从其继承并override几个抽象方法便可复用这样的处理流程。

(2)     通常来说,RoundCacheManager的继承者除了override几个抽象方法外,还会提供一系列的与应用相关的查询方法给外部使用。比如,在我们缘起部分的例子中,我们可以在派生类中设计一个类似下面的方法来提供对某个用户某段时间内的报表数据进行查询:

   ReportData GetUserReportData(string userID, int startDate, int endDate);

(3)     本模型的驱动源在于IIncreaseAutoRetriever的循环引擎,如果其抛出异常而停止运行,那么在此同时,本RoundCacheManager也就丧失了驱动力,后续将再无任何增量进入,Round切换事件NewRoundStarted将永远也不会再触发。

(4)     外部在调用RoundCacheManager的AddRoundCache、RemoveRoundCache方法时要捕获异常,因为持久化过程中可能会出错。

(5)     同介绍IIncreaseAutoRetriever时同样的道理,如果IIncreaseAutoRetriever抛出异常导致引擎停止运行,那么最好的方法就是重启应用程序。当然,这种情况发生的可能性是非常非常微小的。

 

5.扩展

  Round缓存管理器RoundCacheManager暂时没有任何扩展。

 

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

 

时间: 2024-10-24 05:33:49

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

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

1.缘起:     假设我们有一个订单系统,现在这个系统要增加一个功能――允许客人查核他认为有问题的订单的详细信息.当客人觉得自己的某个订单不对劲时,他首先会从订单系统查询这个订单的详细信息,然后打电话告诉我们的客服有问题的订单的编号,客服再去查核,如果属实,客服还要进一步上报,如果该订单非常重要,则可能需要更进一步上报复查等.     从这个需求我们看到,同一个订单可能会在比较短的时间内查询数次甚至数十次,所以我们可以称这个订单为"热点"订单.而其它的成千上万的订单可能在一个月内都不

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

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

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

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

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

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

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

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

对象管理器 IObjectManager -- ESBasic 可复用的.NET类库(12)

1.缘起: 我们经常需要对一些动态对象进行管理,最常见的例子就是在线用户管理.当一个用户成功登陆到服务器后,我们就需要将其管理起来:当他退出后,就不再需要再管理他了.这就是所谓动态对象的含义,这些对象并不是一直需要被管理,只有当其被激活后,才需要被管理.它们总是在"激活"状态和"非激活"状态之间不断地切换. 我设计了对象管理器ESBasic.ObjectManagement.Managers.IObjectManager来管理类似的动态对象.这个类是ESBasic提

分组对象管理器 IGroupingObjectManager--ESBasic 可复用的.NET类库(13)

1.缘起:     假设我们的订单系统需要管理所有未处理的订单,而客人经常需要查询属于自己的未处理的订单列表.另外,可能客服人员也需要根据订单ID迅速地找到对应的未处理订单.基于第一个需求,我们就可以将未处理的订单依据客人的帐号进行分组管理.     我设计了ESBasic.ObjectManagement.Managers.IGroupingObjectManager分组对象管理器来完成对对象进行分组管理的功能.       分组对象管理器的形象示意图如下:      2.适用场合: 当你的需

对象获取器IObjectRetriever -- ESBasic 可复用的.NET类库(17)

1.缘起: ESBasic中许多管理对象的容器都用到了这个ESBasic.ObjectManagement.IObjectRetriever接口,所以单独将其提出来介绍一下. 当我们向对象容器(Container)请求某个对象时,也许目标对象还未加载到容器中,这可能是因为容器在初始化的时候就没有加载这个对象,也有可能是因为这个对象是容器初始化以后新增到数据库(当然也有可能是其它的持久化存储)的.在这种情况下,对象容器就可以借助IObjectRetriever来将目标对象从数据库等持久化存储中加载

循环任务切换器 CircleTaskSwitcher -- ESBasic 可复用的.NET类库(06)

 1.缘起:     假设我的订单处理系统有这样的需求:将一天24小时分为4个时段,凌晨2:15到8:30采用A类型的处理器处理接收到的订单,8:30到14:00采用B类型的处理器,14:00到20:00采用C类型的处理器,20:00到第二天凌晨2:15采用D类型的处理器.     即我们的订单处理器需要在任一天的2:15.8:30.14:00.20:00这四个时刻发生切换,这就是一个循环切换器所要做的工作.     我设计了ESBasic.Threading.Application. ICir