下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

讲解缓存框架,JAD-CACHE架构设计篇

作者:课课家教育     来源: http://www.kokojia.com点击数:663发布时间: 2017-04-25 15:00:57

标签: 数据库设计模式网络工程师

  缓存是提高应用程序性能的最常见的一种技术,常适用于读多写少的场景。按照架构层次可以分为:客户端缓存、页面缓存、应用缓存、持久层缓存。今天我们大家就一起来探讨一下JAD-CACHE架构设计,有需要的小伙伴可以参考一下。

     目前这个框架的编码部分已完成,并取名为JAD-CACHE,取这个名字的原因是因为它是我的个人的JAD项目的一部分,JAD项目是本人用业余时间开发一个企业基础架构平台,因涉及的东西比较多,而且很多模块还没有完全完成。目前准备把其中做的比较完善的缓存模块单独从原项目中剥离出来作为一个独立的项目并准备开源给大家测试和使用,于是也就有了JAD-CACHE。

  本文先展示这个框架的原理及架构设计,后续开放源代码后再发布一些使用手册方面的文章。

  JAD-CACHE缓存框架是在spring cache模块的基础上扩展而来,在上一篇博文《通用缓存框架,spring缓存模块原理分析篇》已经系统分析过srping cache的原理,这里不再重复。spring cache模块重要的两个类就是org.springframework.cache.Cache和 org.springframework.cache.CacheManager。现在我跟据自己的需要对它们进行扩展。

  我设计的Cahce相关的扩展类图如下:

讲解缓存框架,JAD-CACHE架构设计篇_数据库_设计模式_网络工程师_课课家教育

  图: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相关的类图如下:

我们通过它对应的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中都有相应的实现。

  分布式缓存

  缓存系统需要考虑几个问题:

  而由此催发出来的分布式缓存系统则需要额外考虑这几个问题

  成熟的分布式缓存系统有Redis、Memcached、Ehcache、阿里云的OSC等等,各个系统的使用场景和实现方式各不相同这里不再深究。

  系统本身的管理问题,包括了存储空间的分配、扩展、回收机制

  分布式节点管理和路由算法

  缓存键值的管理

  缓存本身的水平线性扩展问题

  缓存大并发下的本身的性能问题

  缓存的单点故障问题(多副本和副本一致性)

  缓存+数据库

  通常我们使用缓存是配合数据库特别是关系型数据库,而数据库则存在主从、读写分离等提高性能和可用性的方案。在这样的情况下,设计缓存系统需要特别注意数据的分发传递速度以及数据库单点故障可能导致的缓存更新失败。没有特殊处理的话可能会引发数据库数据与缓存数据的不一致。

  服务化

  缓存系统需要对外提供统一透明的服务API,你懂的,不解释!

  在AbstractCacheClient中还有一个重要的属性,就是autoStart属性。这个属性如果配置为true,那在spring容器初始化的时候,这个Client实例一但生成,就会自动调用它的start()启动它,以使得它所控制的CacheManager可以正常操作缓存。否则,它不会自动启动,它所控制的所有Cache都处于禁用状态。

  另外,为了方便配置,在AbstractCacheClient还提供了autoCreateCache属性,用于指定此客户端能否自动创建缓存实例。在Spring原来的缓存配置中,需要把用到的每个Cache都写到配置文件中,要么配到ehcache.xml中,要么在配置CacheManager时附加Cache实列相关的配置。否则,在操作没有配置的缓存时,会提示找不到某某名称的cache。在本框架中,如果指定了autoCreateCache属性为true,在调用CacheManager.getCache(String name)获取不到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实例,并注册到spring context中,同时调用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相关的类图如下所示:

在跟据名称找不到任何Cache时,就自动调用这个默认CacheManager的addCache()方法来自动创建一个。本框架CacheManager相关的类图如下所示:

  图:CacheManager类图

  上图灰色总分是spring缓存模块自带的类,其它颜色是扩展类。其中最右边的MasterCacheManager类就是上文提到管理所有CacheManager的实现类。上图左边的JadCacheManager是一个接口,提供initCache(JadCache)和newCache(JadCache)两个方法,这两个方法,主要是用于在CacheClient初始化过程自动生成CacheManager实现类后,准备通过initCache()方法来初始化配置中的Cache来调用的。对于MasterCacheManager类的实例化,开发人员无需额外在spring context中配置,因为在CacheClientManager的初始化过程中,会自动注册一个MasterCacheManager类型的实例到spring context中(上面类图AbstractCacheClientManager这个类中的registerMasterManager()方法就是用来做这个事的)。而这个类中cacheManagerMap的属性就是按名称组织起来的CacheManager类型的集合。在每个CacheClient初始化时自动生成CacheManager实例后,会调用CacheClient类的registryToMasterCacheManager()方法,这个方法会从当前Spring conetxt中获取到MasterCacheManager实例,然后调用它的register()方法,把它注册到MasterCacheManager实例中(添加到cacheManagerMap集合)。

  上面类图中的JadAbstractCacheManger是借用了Srping的AbstractCacheManger对JadCacheManager的一个抽象实现,后面在集成EhCache或MemCache等所有缓存时就从这个类往下扩展。

  而在数据持久之后更新缓存则可能会存在数据持久成功,缓存更新失败的情况,这种情况下需要再次更新缓存或者直接失效缓存,同样需要额外的回馈机制处理缓存更新失败的情况。

  如何选择需要考虑的是对主线业务的影响大小以及缓存脏数据对系统的影响大小,一般来说缓存脏数据并不会造成系统性的影响。

  更多详细内容尽在课课家哦!

赞(22)
踩(0)
分享到:
华为认证网络工程师 HCIE直播课视频教程