Spring缓存模块自身并没有提供Memcache相关的缓存实现,如果要在项目中直接通过Spring注解使用缓存的话,需要自行实现Memcahce相关的缓存管理器。
1、Memcache简介
Memcache作为一套高速缓存系统,跟Ehcache不同的是,它是一个内存缓存服务器程序,通过Memcache协议同客户端交互。Memcache本身并不支持分布式,但是可以通过改造客户端程序,将要缓存的数据的Key进行有规律的封装,使得它支持分布式与集群。在JAVA方面,封装的比较好的客户端有阿里巴巴开源出来的alisoft-xplatform-asf-cache.jar,本文就介绍怎么通过它把Memcache集成到我们的系统中,实现通过Spring注解操作缓存的同时,也实现缓存灵活启停等功能,作为本人之前的相关博文中提到的JAD-CACHE框架的一部分。
2、阿里Memcache客户端原理介绍
阿里开源的Memcache是阿巴巴芩文初大神对Memcache官方推荐的java客户端Whalin进行了优化和改造而成,并在阿里的内部系统中广泛使用。它优化了SockIOPool中存在的一些线程锁的问题,具有更好的性能,同时也对分布式负载均衡提供了支持。此外,为了最大限度的提升性能,在这个客户端还提供了一个基于Map的本地缓存实现,在读取数据时会可以先尝试从本地缓存中读取,本地缓存没有命中时才到Memcache服务器上读取,具体过程可阅读源代码。
使用阿里Memcahce客户端时,需要提供一个xml配置文件,配置缓存服务器等信息,默认的配置文件名称为memcached.xml。对于这个客户端的具体使用,这里不详解,只是指出一些跟集成到Spring相关的一些最基本的元素。
一个最简单的配置例子如下:
<client name="masterCache1" compressEnable="true" defaultEncoding="UTF-8" socketpool="pool0" />
<socketpool name="pool0" failover="true" initConn="5" minConn="5" maxConn="250" maintSleep="0" nagle="false" socketTO="3000" aliveCheck="true">
<servers>127.0.0.1:7775</servers>
</socketpool>
上面配置中显示,需要配置一个client和一个socketpool元素,而且每个client需要配置的一个socketpool属性指向一个socketpool元素。其中client元素的作用就是指定业务系统有哪些缓存可以使用,类似于EhCache配置文件中的一个Cache元素,Memcached在初始化的时候会把配置中的每一个client元素实例化成一个Cache对像,同时跟据socketpool属性初始化它与Memcache服务器的网络连接池。每个Cache对像初始化的时候,也会初始化一个对应的基于Map实现的本地缓存实例(属性名称是locaclCache,对应的具体实现类是com.alisoft.xplatform.asf.cache.impl.DefaultCacheImpl),用于提升性能。
3、Spring集成Memcache的思路
通过上文了解了Memcache客户端的源理及配置后,再考滤集成到Spring中的话就不难了。可以自己写一个Cache实现类,实现spirng的Cache接口,在其中封装一个Memcache的ICache实例。再写一个CacheManager实现类,实现Spring的CacheManager接口,在它的loadCaches()方法中,通过Memcache相关api把它Cache都读初取过来实例化成自己的Cache实现类对像,加入自己实现的CacheManager实现类的Cache集合中就完事了。
4、JAD-CACHE中对Memcache的集成
本人在JAD-CACHE框架对Memcahce集成时除了遵循上一节中提出的方案外,还实现了更多的功能,比如支持随意控制启停,自动创建缓存等等。
另外,考虑到这个Memcache客户端即然实现了一个基于Map的本地缓存,我们为什么不把它单独拿过来作为自己的本地缓存实现方案呢?在我前一篇博文介绍集成Ehcache时附带介绍了一个用Map来做自己的本地缓存,但那个本地缓存是参考Spring自带的本地缓存来做的,因为跟应用虚拟机共用一块内存,有一个问题就是,容易内存溢出,就算开发人员在使用这个本地缓存的时候,配置了一个存活时间以指定数据在缓存中保存的时间。但是缓存的数据就算过期了自己也不会从缓存中清除出去,而是在被应用程序读取出来后发现过期时才被清除,这里是有安全隐患的,万一对像被缓存起来却永过不再被读取呢?那不就一直驻留内存里成为内存溢出的罪魁祸首了。
Memcache的本地缓存在这一点上就做的比较好,它在每个本地缓存实例的内部,都维护了一个后台线程,定时清除列表中过期的对像。基于此,我以为如果一定要用使用本地缓存的话,那么就把这个Memcahce客户端的本地缓存模块拿出来单出使用好了。
考虑到需要自动创建Cache实例的问题,也就是没有配置某个名称的Cache时,自动创建一个。然而,在Memcahce中,每个Cache实例都对应memcahce.xml配置中每一个Client元素,自动创建它的话还需要初始化它对应的socketpool连接池,如果需要负载均衡的话还需要额外处理这些问题。为了解决这些让人蛋痛的问题。我对这个功能做了弱化,就是遇到CacheManager.getCache(String name)获取不到Cache而又需要自动创建的时候,干脆不自动创建了,直接复用一个现成的Cache,往某一个现成的Cahce中塞数据就好了。因为,即然要使用Memcache,那么memcahce.xml配置文件中至少得有一个client。即然这样,可以指定某一个client作为主要的Cache,在需要自动创建Cache时,就直接返回这个实例的引用。这样的就大大简化了配置,开发人员只需要配置一个client就行了,所有数据都往这里面塞就好了,这确实是一个不错的解决方案,但问题也随之来了:那就是缓存Key冲突的问题。为了解决这个问题,可以考虑对key做一个转换,加上缓存名称前缀什么的,比如在往这个Cache中缓存id为1会员数据时,key处理成member1,而往这同一个Cache中缓存id为1的订单数据时,key处理成order1,这样就避免了因为id都为1而引起的缓存冲突。
通过以上分析,及接合前面的JAD-CACHE架构设计,这里给出JAD-CACHE集成Memcache的类图:
图:JAD-CACHE整合memcache类图
上图中,灰色部分,是原来JAD-CACHE框架中现有的类,蓝色部分是本次扩展出来的。注意到上图中有两个CacheManager实现类,分别是LocalCacheManager和MemCacheCacheManager,这两个实现类中第一个是为了把Memcache提供的本地内存为我们单独使用而实现的,另一个是用Memcache真正的缓存。而且,这两个实现类都有一个masterCacheName属性,这是为了方便自动创建实例Cache而使用的(上文有相关分析)。
致此,本人写的JAD-CACHE框架,设计部分就介绍完了,这个框架的代码也写完了,准备开源出来,到时候大家有兴趣的话,配置这些文档再去源代码,就很容易看懂了。开源后,我还会给出详细的使用配置说明。
目前项目已经在开源中国码云平台上开源,代码地址:
https://git.oschina.net/457049726/jad-cache
有兴趣的朋友也可以关注我的公众号: