EHCache讲解

EHCache是来自sourceforge(http://ehcache.sourceforge.net/)的开源项目,也是纯Java实现的简单、快速的Cache组件。EHCache支持内存和磁盘的缓存,支持LRU、LFU和FIFO多种淘汰算法,支持分布式的Cache,可以作为Hibernate的缓存插件。同时它也能提供基于Filter的Cache,该Filter可以缓存响应的内容并采用Gzip压缩提高响应速度。

1. EHCache API的基本用法

1.1 CacheManager类

作用:主要负责读取配置文件,并根据配置文件创建和管理Cache类。默认读取CLASSPATH下的ehcache.xml。

// 使用默认配置文件创建CacheManager
CacheManager manager = CacheManager.create();
// 通过manager可以生成指定名称的Cache对象
Cache cache = cache = manager.getCache("myCache");
// 使用manager移除指定名称的Cache对象
manager.removeCache("myCache");
//可用来移除所有的Cache
manager.removalAll();
//关闭CacheManager
manager.shutdown();

1.2 Cache类

//往cache中添加元素
Element element = new Element("key", "value");
cache.put(element);
//从cache中取回元素
Element element = cache.get("key");
element.getValue();
//从Cache中移除一个元素
cache.remove("key");

注意:对于缓存的对象必须是可序列化的。

1.3 配置文件

配置文件ehcache.xml中命名为myCache的缓存配置:

<cache name="myCache"    //该名字与manager里操作的名字一样
 maxElementsInMemory="10000" // 缓存中允许创建的最大对象数
 eternal="false"             //缓存中对象是否为永久。如果是,超时设置将被忽略,对象从不过期。
 overflowToDisk="true"       //内存不足时,是否启用磁盘缓存

 timeToIdleSeconds="300" //缓存数据的钝化时间,即在一个元素消亡之前,两次访问时间的最大时间间隔值。这只能在元素不是永久驻留时有效,如果该值是 0 就意味着元素可以停顿无穷长的时间。

 timeToLiveSeconds="600" //缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值。这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。

 memoryStoreEvictionPolicy="LFU" //缓存满了之后的淘汰算法。LFU算法直接淘汰使用比较少的对象,在内存保留的都是一些经常访问的对象。对于大部分网站项目,该算法比较适用。

 />

如果应用需要配置多个不同命名并采用不同参数的Cache,可以相应修改配置文件,增加需要的Cache配置即可。

2. 利用Spring APO整合EHCache

首先,在CLASSPATH下面放置ehcache.xml配置文件。在Spring的配置文件中先添加如下cacheManager配置:

<bean id="cacheManager1" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation">
        <value>classpath:ehcache.xml</value>
    </property>
</bean>
// 配置myCache:
<bean id="myCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
    <property name="cacheManager" ref="cacheManager1" />
    <property name="cacheName">
        <value>myCache</value> <!-- 在咖啡 -->
    </property>
</bean>

接下来,写一个实现org.aopalliance.intercept.MethodInterceptor接口的拦截器类。
有了拦截器就可以有选择性的配置想要缓存的 bean 方法。如果被调用的方法配置为可缓存,拦截器将为该方法生成 cache key 并检查该方法返回的结果是否已缓存。如果已缓存,就返回缓存的结果,否则再次执行被拦截的方法,并缓存结果供下次调用。具体代码如下:

public class MethodCacheInterceptor implements MethodInterceptor,
InitializingBean {
    private Cache cache;

    public void setCache(Cache cache) {
        this.cache = cache;
    }

    public void afterPropertiesSet() throws Exception {
        Assert.notNull(cache,"A cache is required. Use setCache(Cache) to provide one.");
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        String targetName = invocation.getThis().getClass().getName();
        String methodName = invocation.getMethod().getName();
        Object[] arguments = invocation.getArguments();
        Object result;
        String cacheKey = getCacheKey(targetName, methodName, arguments);
        Element element = null;
        /**为什么一定要同步?
        Cache对象本身的get和put操作是同步的。
        如果我们缓存的数据来自数据库查询,在没有这段同步代码时,当key不存在或者key对应的对象已经过期时,
        在多线程并发访问的情况下,许多线程都会重新执行该方法,由于对数据库进行重新查询代价是比较昂贵的,
        同步后,如果一个线程从数据库拿到了数据,则后面的线程不需要去数据库再查询了。
        */
        synchronized (this){
            element = cache.get(cacheKey);
            if (element == null) {
                //调用实际的方法
                result = invocation.proceed();
                element = new Element(cacheKey, (Serializable) result);
                cache.put(element);
            }
        }
        return element.getValue();
    }

    private String getCacheKey(String targetName, String methodName,Object[] arguments) {
        StringBuffer sb = 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]);
            }
        }
        return sb.toString();
    }
}

接下来,完成拦截器和Bean的配置:

<bean id="methodCacheInterceptor" class="com.xiebing.utils.interceptor.MethodCacheInterceptor">
    <property name="cache">
        <ref local="myCache" />
    </property>
</bean>
<bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref local="methodCacheInterceptor" />
    </property>
    <property name="patterns">
        <list>
            <!-- 所有以myMethod结尾的方法都会被缓存 -->
            <value>.*myMethod</value>
        </list>
    </property>
</bean>

<bean id="myServiceBean" class="com.xiebing.ehcache.spring.MyServiceBean">
</bean>

<bean id="myService" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref local="myServiceBean" />
    </property>
    <property name="interceptorNames">
        <list>
            <value>methodCachePointCut</value>
        </list>
    </property>
</bean>

使用AOP的方式极大地提高了系统的灵活性,通过修改配置文件就可以实现对方法结果的缓存,所有的对Cache的操作都封装在了拦截器的实现中。

3. CachingFilter功能

作用:可以对HTTP响应的内容进行缓存。这种方式缓存数据的粒度比较粗,例如缓存整张页面。
优点:使用简单、效率高
缺点:不够灵活,可重用程度不高。
EHCache使用SimplePageCachingFilter类实现Filter缓存。该类继承自CachingFilter,有默认产生cache key的calculateKey()方法,该方法使用HTTP请求的URI和查询条件来组成key。也可以自己实现一个Filter,同样继承CachingFilter类,然后覆写calculateKey()方法,生成自定义的key。
有时在使用AJAX时,为保证JS请求的数据不被浏览器缓存,每次请求都会带有一个随机数参数i。如果使用SimplePageCachingFilter,那么每次生成的key都不一样,缓存就没有意义了。这种情况下,我们就会覆写calculateKey()方法。
要使用SimplePageCachingFilter,首先在配置文件ehcache.xml中,增加下面的配置:

<filter>
    <filter-name>SimplePageCachingFilter</filter-name>
    <filter-class>net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>SimplePageCachingFilter</filter-name>
    <url-pattern>/test.jsp</url-pattern>
</filter-mapping>

下面我们写一个简单的test.jsp文件进行测试,缓存后的页面每次刷新,在600秒内显示的时间都不会发生变化的。代码如下:

<%
out.println(new Date());
%>

CachingFilter输出的数据会根据浏览器发送的Accept-Encoding头信息进行Gzip压缩。经过测试,Gzip压缩后的数据量是原来的1/4,速度是原来的4-5倍,所以缓存加上压缩,效果非常明显。

在使用Gzip压缩时,需注意两个问题:
1. Filter在进行Gzip压缩时,采用系统默认编码,对于使用GBK编码的中文网页来说,需要将操作系统的语言设置为:zh_CN.GBK,否则会出现乱码的问题。
2. 默认情况下CachingFilter会根据浏览器发送的请求头部所包含的Accept-Encoding参数值来判断是否进行Gzip压缩。虽然IE6/7浏览器是支持Gzip压缩的,但是在发送请求的时候却不带该参数。为了对IE6/7也能进行Gzip压缩,可以通过继承CachingFilter,实现自己的Filter,然后在具体的实现中覆写方法acceptsGzipEncoding。
具体实现参考:

protected boolean acceptsGzipEncoding(HttpServletRequest request) {
    final boolean ie6 = headerContains(request, "User-Agent", "MSIE 6.0");
    final boolean ie7 = headerContains(request, "User-Agent", "MSIE 7.0");
    return acceptsEncoding(request, "gzip") || ie6 || ie7;
}

4. EHCache在Hibernate中的使用

EHCache可以作为Hibernate的二级缓存使用。在hibernate.cfg.xml中需增加如下设置:

<prop key="hibernate.cache.provider_class">
    org.hibernate.cache.EhCacheProvider
</prop>

然后在Hibernate映射文件的每个需要Cache的Domain中,加入类似如下格式信息:

<cache usage="read-write|nonstrict-read-write|read-only" />

比如:

<cache usage="read-write" />

最后在配置文件ehcache.xml中增加一段cache的配置,其中name为该domain的类名。

<cache name="domain.class.name"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="false"
/>

5. EHCache的监控

对于Cache的使用,除了功能,在实际的系统运营过程中,我们会比较关注每个Cache对象占用的内存大小和Cache的命中率。有了这些数据,我们就可以对Cache的配置参数和系统的配置参数进行优化,使系统的性能达到最优。EHCache提供了方便的API供我们调用以获取监控数据,其中主要的方法有:

//得到缓存中的对象数
cache.getSize();
//得到缓存对象占用内存的大小
cache.getMemoryStoreSize();
//得到缓存读取的命中次数
cache.getStatistics().getCacheHits()
//得到缓存读取的错失次数
cache.getStatistics().getCacheMisses()

6. 分布式缓存

EHCache从1.2版本开始支持分布式缓存。分布式缓存主要解决集群环境中不同的服务器间的数据的同步问题。具体的配置如下:
在配置文件ehcache.xml中加入

<cacheManagerPeerProviderFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
    properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1, multicastGroupPort=4446"/>

<cacheManagerPeerListenerFactory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"/>

另外,需要在每个cache属性中加入

<cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/>

例如:

<cache name="demoCache"
maxElementsInMemory="10000"
eternal="true"
overflowToDisk="true" >
   <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/>
</cache>

总结

EHCache是一个非常优秀的基于Java的Cache实现。它简单、易用,而且功能齐全,并且非常容易与Spring、Hibernate等流行的开源框架进行整合。通过使用EHCache可以减少网站项目中数据库服务器的访问压力,提高网站的访问速度,改善用户的体验。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页