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

1.缘起:

    为了提升系统的性能或减轻数据库的压力等原因,我们经常在系统中使用缓存来把那些经常使用的数据保留在内存中。如果因为某些原因,缓存中这些经常使用的数据不能及时与数据源进行同步更新,那么采用定时刷新缓存中的数据有可能就是一种合适的选择。

    如果你的缓存是定时刷新,那么你就需要自己为其维护一个定时器或循环引擎。如果你的系统中像这样定时刷新的缓存有多个,而且每个缓存定时刷新的时间间隔又要求不一样,那么,使这些缓存按照你预想的情况进行运转,你就需要花费一些气力。

    我设计了定时刷新缓存管理器IRefreshableCacheManager来帮助你管理你系统中所有需要进行定时刷新的缓存。它可以根据每个缓存的刷新时间间隔要求,在正确的时刻,刷新其所管理的缓存。你不用再自己手动去处理定时器或循环引擎,IRefreshableCacheManager自动为你完成这一切。

 

2.适用场合:

(1)系统中需要使用一个或多个需要进行定时刷新的缓存。

(2)每个缓存所要求的刷新时间间隔可能都不一样。

(3)刷新的时间间隔并不要求精确。

 

3.设计思想与实现

    本节所讲述的是定时刷新缓存管理器IRefreshableCacheManager,其重点在于“管理器”,而不是在于“缓存”,这点是必须清楚的。缓存只是被管理器管理的对象。

能够被IRefreshableCacheManager管理的缓存必须是可定时刷新的缓存,也就是说,这些缓存必须实现IRefreshableCache接口以表明自己可以接受管理器的管理。IRefreshableCache定义如下:

    public interface IRefreshableCache
    {
        /// <summary>
        /// RefreshSpanInSecs 定时刷新的时间间隔(秒)。如果设置为0,则表示与IRefreshableCacheManager的刷新时间统一。
        /// </summary>
        int RefreshSpanInSecs { get; }

        /// <summary>
        /// LastRefreshTime 最后一次刷新时间。
        /// </summary>
        DateTime LastRefreshTime { get; set; }        
        
        void Refresh();       
    }

这个接口相当简单,它只要求缓存提供Refresh方法进行刷新,并通过RefreshSpanInSecs属性表示出自己希望进行定时刷新的时间间隔。如果这个属性设置为0(默认值),则表示该缓存接受管理器的统一调度。

LastRefreshTime属性用于记录最后一次刷新结束的时间,该属性的值由管理器进行设置。

我们从IRefreshableCache接口的定义看到,管理器不用关心与缓存中的数据相关的任何信息,这是由具体的应用在实现IRefreshableCache接口时才指定的。

接下来,我们看看IRefreshableCacheManager接口的定义:

    public interface IRefreshableCacheManager
    {
        /// <summary>
        /// RefreshSpanInSecs 定时刷新缓存的时间间隔。
        /// </summary>
        int RefreshSpanInSecs { set; }

        IList<IRefreshableCache> CacheList { set; }

        void Initialize();

        /// <summary>
        /// RefreshNow 手动刷新被管理的所有缓存。
        /// </summary>
        void RefreshNow();

        /// <summary>
        /// AddCache 动态添加缓存。
        /// </summary>       
        void AddCache(IRefreshableCache cache);

        /// <summary>
        /// RemoveCache 动态移除缓存。
        /// </summary>       
        void RemoveCache(IRefreshableCache cache);      

        /// <summary>
        /// CacheRefreshFailed 当某个缓存刷新抛出异常时,将触发该事件。
        /// </summary>
        event CbCacheException CacheRefreshFailed;
    }

这个接口也有一个RefreshSpanInSecs属性,如果被管理的缓存的RefreshSpanInSecs属性设置的是0,那么管理器将用自身的这个属性对其进行调度。IRefreshableCacheManager接口的RefreshSpanInSecs属性是为了简化那种被管理的所有缓存都采用统一刷新时间间隔的情况而存在的。

      RefreshableCacheManager在实现时仍然是借助前面介绍的循环引擎AgileCycleEngine来进行定时控制的。

IRefreshableCacheManager提供了AddCache和RemoveCache方法用于在运行中动态的添加或移除缓存。

当某个缓存在刷新时,抛出异常,则IRefreshableCacheManager会触发CacheRefreshFailed事件,事件参数包含了出现异常的缓存和异常对象。

 

    关于RefreshableCacheManager的实现,要注意以下几点:

(1)管理器的实现是线程安全的,可以使用于多线程的环境中。我们对其内部的缓存列表进行了加锁控制。

(2)管理器在初始化(Initialize)时,启动了循环引擎(其DetectSpanInSecs必须设置为1秒),而且,将所有被管理的缓存的LastRefreshTime都设置为当前时间。

(3)在实现EngineAction方法时,必须在foreach块中使用try来捕捉引擎刷新时抛出的异常,而不是在try块中进行foreach。这个顺序是重要的,如果在try块中进行foreach,那么当一个缓存在刷新时抛出异常而导致foreach中断后,后续缓存的刷新方法都将不会被检测和调用。

(4)使用锁不仅仅是为了同步对内部缓存列表集合的修改,手动调用刷新方法RefreshNow也需要被同步,否则就可能出现两个线程同时进行检测和刷新缓存的情况(一个是循环引擎的线程,另一个是手动调用RefreshNow方法的线程)。

 

4. 使用时的注意事项

(1)由于管理器内部实现采用了循环引擎,所以定时刷新的时间间隔不可能很精确,而且,针对每个缓存的刷新方法的顺序调用也是导致这种不精确的另一个原因。

(2)也由于管理器内部实现采用了循环引擎,循环引擎能设置的最小检测时间间隔为1秒,所以缓存的刷新时间间隔也不可能小于1秒。

(3)如果某个缓存在刷新时抛出异常,那么其LastRefreshTime属性还是记录的上一次成功刷新的时间。

 

5.扩展

     定时刷新缓存管理器通过事件暴露缓存刷新失败的通知,当缓存刷新发生异常时,我们可能需要将异常记录到日志中。如果是这样,那就可以直接使用ESBasic提供的RefreshableCacheExceptionLogBridge来完成。

       RefreshableCacheExceptionLogBridge借助ESBasic.Logger.IAgileLogger组件来将异常的详细信息记录到目标日志中。日志可以是文本文件,也可以是数据库等其他存储。ESBasic提供了IAgileLogger接口的实现FileAgileLogger,用于将日志写入文本文件中。

 

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

 

时间: 2024-09-17 16:03:08

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

热缓存 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定位节点的访问.所

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

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

优先级管理器 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.适用场合: 当你的需

定时任务管理器 TimingTaskManager -- ESBasic 可复用的.NET类库(08)

1.缘起:     假设我们的报表系统需要在每天的00:05:00统计前一天的报表数据,需要在每周一的00:30:00统计上周的报表数据,又需要在每月1日的00:30:00统计上月的报表数据. 这些报表统计任务是很常见的系统需求,对于类似这样的在指定时刻执行的定时任务,我使用ESBasic.Threading.Timers.TimingTaskManager(定时任务管理器)来处理它. TimingTaskManager与前面讲的回调定时器CallbackTimer的区别在于,CallbackT

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

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