1.缘起:
为了提升系统的性能或减轻数据库的压力等原因,我们经常在系统中使用缓存来把那些经常使用的数据保留在内存中。如果因为某些原因,缓存中这些经常使用的数据不能及时与数据源进行同步更新,那么采用定时刷新缓存中的数据有可能就是一种合适的选择。
如果你的缓存是定时刷新,那么你就需要自己为其维护一个定时器或循环引擎。如果你的系统中像这样定时刷新的缓存有多个,而且每个缓存定时刷新的时间间隔又要求不一样,那么,使这些缓存按照你预想的情况进行运转,你就需要花费一些气力。
我设计了定时刷新缓存管理器IRefreshableCacheManager来帮助你管理你系统中所有需要进行定时刷新的缓存。它可以根据每个缓存的刷新时间间隔要求,在正确的时刻,刷新其所管理的缓存。你不用再自己手动去处理定时器或循环引擎,IRefreshableCacheManager自动为你完成这一切。
2.适用场合:
(1)系统中需要使用一个或多个需要进行定时刷新的缓存。
(2)每个缓存所要求的刷新时间间隔可能都不一样。
(3)刷新的时间间隔并不要求精确。
3.设计思想与实现
本节所讲述的是定时刷新缓存管理器IRefreshableCacheManager,其重点在于“管理器”,而不是在于“缓存”,这点是必须清楚的。缓存只是被管理器管理的对象。
能够被IRefreshableCacheManager管理的缓存必须是可定时刷新的缓存,也就是说,这些缓存必须实现IRefreshableCache接口以表明自己可以接受管理器的管理。IRefreshableCache定义如下:这个接口相当简单,它只要求缓存提供Refresh方法进行刷新,并通过RefreshSpanInSecs属性表示出自己希望进行定时刷新的时间间隔。如果这个属性设置为0(默认值),则表示该缓存接受管理器的统一调度。
{
/// <summary>
/// RefreshSpanInSecs 定时刷新的时间间隔(秒)。如果设置为0,则表示与IRefreshableCacheManager的刷新时间统一。
/// </summary>
int RefreshSpanInSecs { get; }
/// <summary>
/// LastRefreshTime 最后一次刷新时间。
/// </summary>
DateTime LastRefreshTime { get; set; }
void Refresh();
}
LastRefreshTime属性用于记录最后一次刷新结束的时间,该属性的值由管理器进行设置。
我们从IRefreshableCache接口的定义看到,管理器不用关心与缓存中的数据相关的任何信息,这是由具体的应用在实现IRefreshableCache接口时才指定的。
接下来,我们看看IRefreshableCacheManager接口的定义:这个接口也有一个RefreshSpanInSecs属性,如果被管理的缓存的RefreshSpanInSecs属性设置的是0,那么管理器将用自身的这个属性对其进行调度。IRefreshableCacheManager接口的RefreshSpanInSecs属性是为了简化那种被管理的所有缓存都采用统一刷新时间间隔的情况而存在的。
{
/// <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;
}
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,用于将日志写入文本文件中。