1.缘起:
假设我们有一个会员管理系统,需要向各方提供查询会员基础资料的功能。会员一经注册,其基础资料就将不再发生变化(如会员帐号、身份证ID、注册时间等等)。
基于这样的需求,我们可以将会员的基础资料“永久地”缓存在内存中,从而提升对任何一个会员基础资料的查询速度。
我设计了ESBasic.ObjectManagement.Cache.ISmartDictionaryCache来对这种性质的对象进行缓存。
职能字典缓存的形象示意图如下:
2.适用场合:
在使用ISmartDictionaryCache之前,必须满足以下条件:
(1)将要被缓存的每个对象都有唯一的ID。
(2)被缓存的对象一旦“出生”就是恒定不变的。
(3)对象永远不会被销毁,或者说即使被销毁了在缓存中仍然存在也无关紧要。
3.设计思想与实现
ISmartDictionaryCache的接口定义如下:
public interface ISmartDictionaryCache<Tkey ,TVal>
{
IObjectRetriever<Tkey, TVal> ObjectRetriever { set; }
int Count { get; }
void Initialize();
/// <summary>
/// Get 如果缓存中不存在id对应的object,则采用ObjectRetriever提取一次,如果仍然提取不到则返回null。
/// </summary>
TVal Get(Tkey id);
void Clear();
/// <summary>
/// HaveContained 当前容器是否已经存在目标对象。
/// </summary>
bool HaveContained(Tkey id);
IList<TVal> GetAllValListCopy();
IList<Tkey> GetAllKeyListCopy();
}
ISmartDictionaryCache依赖于我们前面介绍的IObjectRetriever接口,这就表示智能字典缓存是通过IObjectRetriever来将对象加载到缓存中来的。
Initialize方法执行时,会将所有对象都加载到内存中。
当外部调用Get方法时,如果目标对象在缓存中不存在,缓存则会调用IObjectRetriever加载目标对象。如果目标对象在持久化存储中都不存在,就会返回null。
注意,ISmartDictionaryCache并没有提供任何将对象移除缓存的方法,这表明对象一旦加载到缓存中,就将一直存在直到系统停止运行。
ISmartDictionaryCache接口的实现是简单的。同样的,SmartDictionaryCache可以在多线程的环境中使用,因为我们在实现时对内部集合ditionary进行了加锁控制。
4. 使用时的注意事项
(1)在使用智能字典缓存时,要考虑到对象的数量和对象的尺寸大小的问题。因为如果对象的数量巨大,而且对象的个头也很大,那将占用很多的内存空间。
(2)如果缓存中的对象被访问的概率不是一样的,而是由明显的差别,比如有绝大部分对象从来不被访问,有的对象被密集访问,那这个时候可以考虑使用我们后面即将介绍的“热缓存”IHotCache来代替智能字典缓存。
(3)智能字典缓存在初始化时调用了IObjectRetriever接口的RetrieveAll方法,但是正如我们在介绍IObjectRetriever时提到的,在实现IObjectRetriever接口的RetrieveAll方法,不一定要真正的返回所有的对象,你可以只返回那些你认为将会被使用到的对象。甚至,你可以返回一个不包含任何元素的集合,这样,智能字典缓存就只会缓存那些至少被访问过一次的对象了。在某些系统中,这也许可以有效地节省一些内存空间。
5.扩展
智能字典缓存ISmartDictionaryCache只能缓存恒定不变的对象?嗯,这一点是千真万确的。但是,有些情况我们可以灵活处理。
还是以我们在缘起部分提到的那个例子继续讲解,让我们在其基础上,再假设一个会员(Member)的绝大部分资料是在注册时就确定下来不会发生改变的,但是有小部分资料(比如对应这数据库中Member表中的几个列)是会发生变化的,比如会员的额度、密码、最后一次访问时间等这样的信息。当外部向系统请求这些非恒定信息时,我们就无法从智能字典缓存中获取了,因为智能字典缓存中的目标会员的这些字段可能已经不是正确的值了。
基于这样的情况,我们仍然是可以使用智能字典缓存的。这里我提出一个SubObject(“子对象”)的概念。所谓“子对象”就是由某对象的一部分属性构成的一个新的对象。
所以,我们可以将Member中恒定不变的部分抽出来形成一个子对象,如SubMember,那么SubMember就符合了使用ISmartDictionaryCache进行缓存条件。当外部要查询会员的基础资料属于恒定部分时,我们就可以非常快速地从ISmartDictionaryCache获取返回。
对于像Member这样由恒定部分和非恒定部分构成的对象来说,除了使用这种方式进行缓存之外,还有其它的办法。但由于超出了本节的主题,我们会在其它小节进行介绍。
注: ESBasic已经开源,点击这里下载源码。
ESBasic开源前言