在之前一篇《写一个自己的通用缓存框架,以同时支持ehcache、mecache以及springcache注解等等》博文中,列出了自己的通用缓存框架需要实现在的大致功能总结如下:
1、提供统一的缓存操作api;
2、支持同时使用多种缓存实现;
3、提供灵活的配置;
4、需要防止缓存穿透;
5、需要可以灵活指定缓存存活时间;
6、需要任意控制缓存的停用或启用。
目前这个框架的编码部分已完成,并取名为JAD-CACHE,取这个名字的原因是因为它是我的个人的JAD项目的一部分,JAD项目是本人用业余时间开发一个企业基础架构平台,因涉及的东西比较多,而且很多模块还没有完全完成。目前准备把其中做的比较完善的缓存模块单独从原项目中剥离出来作为一个独立的项目并准备开源给大家测试和使用,于是也就有了JAD-CACHE。
本文先展示这个框架的原理及架构设计,后续开放源代码后再发布一些使用手册方面的文章。
JAD-CACHE缓存框架是在springcache模块的基础上扩展而来,在上一篇博文《通用缓存框架,spring缓存模块原理分析篇》已经系统分析过srpingcache的原理,这里不再重复。spring cache模块重要的两个类就是org.springframework.cache.Cache和 org.springframework.cache.CacheManager。现在我跟据自己的需要对它们进行扩展。
我设计的Cahce相关的扩展类图如下:
图:Cahce扩展类类图1
上图中灰色部分是spring自已的类,其它的是扩展出来的。上图ManageredCache是一个接口,表示这个Cache可由本框架的CacheClient实例管理起来(关于CacheClient的概念稍后介绍)。ManageredCache接口在父接口Cache的基础上,增加了isAllowNullValues()等等方法,作用分别如下:
getCacheClient()
获得管理它的CacheClient实例
isAllowNullValues()
能否在此缓存中保存null值,防止缓存穿透
getActivityTime()
获得此缓存中对像存活时间,注意这个存活时间是一个业务存活时间,开发人员可通过配置指定一个默认的时间,也可以在调用put()方法缓存对像时通过参数另外指定一个存活时间。这样,在调用get()方法从缓存中取出对像后,会先通过个这个方法判断对像是否失效(即使它依旧存在于缓存中,但我们可以在业务的角度上以为它失效了),这个存活时间应该短于用户在ehcache.xml等配置文件配置的存活时间,这样就实现了个性化指定同一类型不同对像的存活时间。
put(Object, Object,int)
这个方法跟父类Cache接口的put(Object,Object)功能相同,就是把对像缓存起来,但它支持一个int类型的参数,用于指定对像存活时间的秒数。
size()
统计对像总数
keySet()
获得所有key
所有要缓存的数据都不是直接持久化到缓存容器中的,而是被装包成了一个个CacheValue类型的实例,在上图的类图中,可以看到,CacheValue类包含两个属性expiryTime和value,其中expiryTime是超时时间,value就是据体的数据对像。如果数据对像为null值,就转换成NullCacheData实例。在AbstractManageredCache这个抽象类的相关方法中,就实现了这些逻辑。
AbstractManageredCache是对ManageredCache接口的抽象实现,实现了在操作缓存之前,先通过管理它的CacheClient实例判断当前缓存客户端的状态是否已开启 (调用cacheClient的isStarted()方法),如果启用,就调用父接口Cache中声明的getNativeCache()获得具体的缓存实例操作缓存,如果没有启用,就什么也不做。同时,在它的put()方法实现过程中,会跟据它的activityTime属性指定存活时间,或跟据allowNullValues属性决定是否缓存null值,并将所有要缓存的数据包装成CacheValue实例持久化到缓存容器中。而在get()方法实现的罗辑,也会做相应的操作,跟据activityTime属性踢出超时的对像,并将缓存容器中的CacheValue实例转换成原始的数据类型。
在 spring 原来的缓存模型中,所有的 Cache 实例都被 ManagerCache 所管理。但本框架抽像出了一个叫 CacheClient 的概念,所有的缓存实例都被扩展成了 ManageredCache 对像,并被一个 CacheClient 实例管理起来 ( 而此 CacheClient 实例又持有一个 CacheManager 的引用,这样一来 Cache 也就可以通过 CacheClient 间接的被 ManagerCache 管理 ) 。这样做有一个好处就是:在 Cache 操作缓存之前,先通过它的 CacheClient 判断当前的缓存状态,跟据这个装态决定是否要进行操作。设计 CacheClinet 还有一个目地,就是让多个缓存实现能更好的共存于同一个应用之中,比如让 EhCache 实现的缓存交给一个 CacheClinet 管理,让 MemCache 实现的缓存交给另一个 CacheClinet 管理。在 spring 原来的缓存模块中,设计了一个叫 CompositeCacheManager 的类,可以同时配置多个 CacheManager 实例以达到这个目地。但我弃用它,改用 CacheClinet 的目地,就是想增加可以任意停用或启用某些 Cache 的功能。比如,当 memcache 服务器挂掉时,我们通过它对应的 CacheClinet 实例改变这个实例管理的所有 Cache 的状态,停用它,从而达到从应用层上禁用或启用缓存的目地。 CacheClient 相关的类图如下:
图:CacheClient相关类图
上图中的CacheClient类是一个接口,它声明的一些诸如start(),stop()等方法,用于修改本实例的状态,启用或停用,而isStarted()就是用来检查状态的,返回当前Client是否启用。每一个CacheClient实例管理一个CacheManager,通过该接口中的getCacheManager()可以获得它所控制的CacheManager实例引用,每个一个实例有一个唯一的名字,通过getClientName()可以获取它的名字。除此外,此接口还有一些诸如getAllowNullValues(),setDefActivityTime()之类的方法。这是为了方便开发人员对缓存的配置,开发人员在配置CacheClient实例时,可以在这里配置allowNullValues,defActivityTime等属性,这样再在配置CacheManager或者Cache实例时就可以不指定了这些属性了,Cache会自动继承它的Client的属性值。这些配置在CacheClient接口的抽像实现类AbstractCacheClient中都有相应的实现。
在AbstractCacheClient中还有一个重要的属性,就是autoStart属性。这个属性如果配置为true,那在spring容器初始化的时候,这个Client实例一但生成,就会自动调用它的start()启动它,以使得它所控制的CacheManager可以正常操作缓存。否则,它不会自动启动,它所控制的所有Cache都处于禁用状态。
另外,为了方便配置,在AbstractCacheClient还提供了autoCreateCache属性,用于指定此客户端能否自动创建缓存实例。在Spring原来的缓存配置中,需要把用到的每个Cache都写到配置文件中,要么配到ehcache.xml中,要么在配置CacheManager时附加Cache实列相关的配置。否则,在操作没有配置的缓存时,会提示找不到某某名称的cache。在本框架中,如果指定了autoCreateCache属性为true,在调用CacheManager.getCache(Stringname)获取不到Cache时,会自动创建一个默认的(通过覆盖CacheManager.getMissgeCache()实现)。当然,如果指定autoCreateCache为false时,就不会自动创建,这要求用户在ehcache.xml中自己配置。当然,为了方便,本框架,还可以直接在CacheClient中配置,上面的类图AbstractCacheClient中两个属性cacheNames和cacheBeans就是用于这个配置的。用户可通过cacheNames只配置名称,或者通过cacheBeans属性配置一个类型为的JadCache实例bean。这个JadCache类似于spring在实现ehcache时提供的EhCacheFactoryBean。只不过这个更加简洁通用。如果只通过cacheNames配置一个Cache的名称,那么此Cache实例的相关属性都采用默认值这要求用户在ehcache.xml配置文件中配置一个defaultCache,或需要在memcache.xml中配置一个默认的cacheclient。
每一个CacheClient唯一管理一个CacheManager实例(在上面类图中可以看到,AbstractCacheClient类中有一个JadCacheManager类型的属性:cacheManager。关于本框架的CacheManager扩展稍后介绍)。CacheClient在被spring初始化时,会自动调用registryCacheManager()方法,跟据不同的缓存实现厂商生成一个对应的CacheManager实例,并注册到springcontext中,同时调用initCache()方法通过配置中的cacheNames或cacheBeans自动初始化所有的Cache实例。初始化完成后,再调用registryToMasterCacheManager()方法,这个方法稍后介绍。
一个应用系统中,可以有一个或多个CacheClient实例,每个CacheClient实例控制一个CacheManger。开发人员通过配置不同的CacheClient实例可以实现同时支持多个不同的缓存实现,比如,把EhCache相关的缓存配置到一个CacheClient中而把MemCache配置到另一个CacheClient中。
为了统一管理所有CacheClient实例,本框架设计了一个叫CacheClientManager来管理所CacheClient实例,它有一个抽像实现AbstractCacheClientManager,这个抽像类中,也有allowNullValues,defActivityTime等CacheClient中具的相同的属性,只不过这里的属性作为一个全局的配置,使得CacheClient可以省去这些配置而直接使用CacheClientManager中的配置。整个应用中,只能有CacheClientManager实例,但这个实例可以管理一个或多个CacheClient。而通常况下,一些简单的应用系统中,往往只有一种缓存实现,也就是只需要配置一个CacheClient,因此AbstractCacheClientManager给出了两个不同的实现类,分别是上图中红颜色的SingleClientManager和MultiClientManager,分别表示单CacheClient管理或多CacheClient管理器。开发人员可跟据业务情况选择性使用其中一个进行配置。
前文提到,每个 CacheClient 在初始化时,会自动生成一个对应的 CacheManager 实例并注册到 Spring 上下文中,但 Spring 在操作缓存时,为了能准确的通过 CacheManager 找到相应名称的 Cache 实例,这就要求还需要对这些 CacheManager 实例进行统一管理。在 spring 原来的缓存模块中,提供了一个叫 CompositeCacheManager 的实现类,以组合设计模式的方式来管理这些 CacheManager 实例,这个 CompositeCacheManager 实现类有一个列表指向所有 CacheManager 实例的引用。在执行 getCache(String) 时会遍历这个列表,循环调用每个实例的 getCache(String) 方法,然后返回一个不为 null 的 Cache ,但是如果所有的 CacheManager 都获取不到 Cache 时,这个方法最终是会返回 null 的。然而,在本框架中,是支持找不到 Cache 时自动创建的,所以在本框架中,我设计了一个叫 MasterCacheManager 的类来管理这些 CacheManager ,同时,这个类有一个叫 defCacheManager 的 CacheManager 属性来指定一个默认的 CacheManager 。在跟据名称找不到任何 Cache 时,就自动调用这个默认 CacheManager 的 addCache() 方法来自动创建一个。本框架 CacheManager 相关的类图如下所示:
图:CacheManager类图
上图灰色总分是spring缓存模块自带的类,其它颜色是扩展类。其中最右边的MasterCacheManager类就是上文提到管理所有CacheManager的实现类。上图左边的JadCacheManager是一个接口,提供initCache(JadCache)和newCache(JadCache)两个方法,这两个方法,主要是用于在CacheClient初始化过程自动生成CacheManager实现类后,准备通过initCache()方法来初始化配置中的Cache来调用的。对于MasterCacheManager类的实例化,开发人员无需额外在spring context中配置,因为在CacheClientManager的初始化过程中,会自动注册一个MasterCacheManager类型的实例到springcontext中(上面类图AbstractCacheClientManager这个类中的registerMasterManager()方法就是用来做这个事的)。而这个类中cacheManagerMap的属性就是按名称组织起来的CacheManager类型的集合。在每个CacheClient初始化时自动生成CacheManager实例后,会调用CacheClient类的registryToMasterCacheManager()方法,这个方法会从当前Spring conetxt中获取到MasterCacheManager实例,然后调用它的register()方法,把它注册到MasterCacheManager实例中(添加到cacheManagerMap集合)。
上面类图中的JadAbstractCacheManger是借用了Srping的AbstractCacheManger对JadCacheManager的一个抽象实现,后面在集成EhCache或MemCache等所有缓存时就从这个类往下扩展。
以上就是本框架的一个总体设计,因为篇附过长。后面再在其它的文章中给出在这个基础上扩展EhCache和MemCache的方案,并实现一个基于Map的内存缓存实现。
目前JAD-CACHE项目已在开源中国码云平台上开源,地址:
https://git.oschina.net/457049726/jad-cache
想关注更多信息或者想及时了解动态的同学们可以扫以下二维码关注我的微信公众号,多谢