在开发高并发量,高性能的网站应用系统时,缓存Cache起到了非常重要的作用。本文主要介绍EHCache的使用,以及使用EHCache的实践经验。
笔者使用过多种基于Java的开源Cache组件,其中包括OSCache、JBossCache、EHCache。OSCache功能强大,使用灵活,可用于对象缓存、Filter缓存以及在JSP中直接使用cache标签。笔者在最近的使用过程中发现,在并发量较高时,OSCache会出现线程阻塞和数据错误,通过分析源代码发现是其内部实现的缺陷。JBossCache最大的优点是支持基于对象属性的集群同步,不过JBossCache的配置使用都较复杂,在并发量较高的情况下,对象属性数据在集群中同步也会加大系统的开销。以上两种Cache本文仅作简单介绍,不做深入探讨。
EHCache是来自sourceforge(http://ehcache.sourceforge.net/)的开源项目,也是纯Java实现的简单、快速的Cache组件。EHCache支持内存和磁盘的缓存,支持LRU、LFU和FIFO多种淘汰算法,支持分布式的Cache,可以作为Hibernate的缓存插件。同时它也能提供基于Filter的Cache,该Filter可以缓存响应的内容并采用Gzip压缩提高响应速度。
l EHCache API的基本用法
首先介绍CacheManager类。它主要负责读取配置文件,默认读取CLASSPATH下的ehcache.xml,根据配置文件创建并管理Cache对象。
//使用默认配置文件创建CacheManager
CacheManagermanager = CacheManager.create();
//通过manager可以生成指定名称的Cache对象
Cache cache =cache = manager.getCache("demoCache");
//使用manager移除指定名称的Cache对象
manager.removeCache("demoCache");
可以通过调用manager.removalAll()来移除所有的Cache。通过调用manager的shutdown()方法可以关闭CacheManager。
有了Cache对象之后就可以进行一些基本的Cache操作,例如:
//往cache中添加元素
Elementelement = new Element("key", "value");
cache.put(element);
//从cache中取回元素
Elementelement = cache.get("key");
element.getValue();
//从Cache中移除一个元素
cache.remove("key");
可以直接使用上面的API进行数据对象的缓存,这里需要注意的是对于缓存的对象都是必须可序列化的。在下面的篇幅中笔者还会介绍EHCache和Spring、Hibernate的整合使用。
l 配置文件
配置文件ehcache.xml中命名为demoCache的缓存配置:
<cachename="demoCache"
maxElementsInMemory="10000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"/>
各配置参数的含义:
maxElementsInMemory:缓存中允许创建的最大对象数
eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。
timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0 就意味着元素可以停顿无穷长的时间。
timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。
overflowToDisk:内存不足时,是否启用磁盘缓存。
memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。LRU和FIFO算法这里就不做介绍。LFU算法直接淘汰使用比较少的对象,在内存保留的都是一些经常访问的对象。对于大部分网站项目,该算法比较适用。
如果应用需要配置多个不同命名并采用不同参数的Cache,可以相应修改配置文件,增加需要的Cache配置即可。
l 利用Spring APO整合EHCache
首先,在CLASSPATH下面放置ehcache.xml配置文件。在Spring的配置文件中先添加如下cacheManager配置:
<beanid="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
</bean>
配置demoCache:
<beanid="demoCache"class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<propertyname="cacheManager" ref="cacheManager" />
<propertyname="cacheName">
<value>demoCache</value>
</property>
</bean>
接下来,写一个实现org.aopalliance.intercept.MethodInterceptor接口的拦截器类。有了拦截器就可以有选择性的配置想要缓存的bean 方法。如果被调用的方法配置为可缓存,拦截器将为该方法生成 cache key并检查该方法返回的结果是否已缓存。如果已缓存,就返回缓存的结果,否则再次执行被拦截的方法,并缓存结果供下次调用。具体代码如下:
public classMethodCacheInterceptor implements MethodInterceptor,
InitializingBean{
private Cachecache;
public voidsetCache(Cache cache) {
this.cache =cache;
}
public voidafterPropertiesSet() throws Exception {
Assert.notNull(cache,
"A cacheis required. Use setCache(Cache) to provide one.");
}
public Objectinvoke(MethodInvocation invocation) throws Throwable {
StringtargetName = invocation.getThis().getClass().getName();
StringmethodName = invocation.getMethod().getName();
Object[]arguments = invocation.getArguments();
Objectresult;
StringcacheKey = getCacheKey(targetName, methodName, arguments);
Elementelement = null;
synchronized(this){
element =cache.get(cacheKey);
if (element== null) {
//调用实际的方法
result =invocation.proceed();
element = newElement(cacheKey, (Serializable) result);
cache.put(element);
}
}
returnelement.getValue();
}
privateString getCacheKey(String targetName, String methodName,
Object[]arguments) {
StringBuffersb = new StringBuffer();
sb.append(targetName).append(".").append(methodName);
if((arguments != null) && (arguments.length != 0)) {
for (int i =0; i < arguments.length; i++) {
sb.append(".").append(arguments[i]);
}
}
returnsb.toString();
}
}