缓存之EhCache学习

缓存学习之EhCache

1.      理解

很多时候,数据不需要总是去查数据库,特别是访问频率很高、对实时性要求不高的数据,别如一些静态页面的网页元素、webservice提供的数据,这些数据并不需要实时,相反,如果实时的话,当访问量很大时,会对数据库造成很大的压力,所以我们可以考虑使用缓存,当然,这个缓存的超时时间可以短一点,这样也能做到伪实时。

常见的缓存方案有2种,即文件缓存和内存缓存,当数据量较小时,可以使用内存缓存,相反,则建议使用文件缓存。

内存缓存,最常用的就是搞一个静态map,把数据存入其中,并在适当的时候维护map的数据即可。这样的好处是方便,轻量级,坏处是耗内存,且功能不全,比如分布式能力。

文件缓存,其实并没有单纯的文件缓存解决方案,原因很明显,I/O速度太慢,所以这里提到的文件缓存,其实是内存缓存+文件缓存的总称,简单的说就是默认用内存,当超过一定量的时候就存入文件,需要的时候再取出来。这种文件缓存,通常都是由某个厂家提供完整的解决方案的,常见的有OSCache、EhCache、Jcache、Jbosscache等,本文将重点介绍EhCache。

缓存是一把双刃剑,需要综合考虑命中率和性能代价,所以,需要根据业务设计如何使用缓存。

2.     单机环境下使用EhCache

2.1           准备工作

演示版本:ehcache-2.8.2-distribution.tar.gz

Lib:把源码包lib目录下的ehcache-2.8.2.jar、slf4j-api-1.6.6.jar、slf4j-jdk14-1.6.6.jar3个jar包放入工程中,同时把ehcache.xml和ehcache.xsd拷贝到WEB-INF/classes下

2.2           项目背景

做了一个适配器,它的功能是提供webservice服务(Restlet),供用户查询指定的DTS数据(单个或者全部)。考虑到并发性,且对DTS数据的实时性要求不是很高,所以决定使用缓存,每个3分钟失效一次,这样可以做到伪实时。查询数据的rest接口地址如下:/dtsrest/query/DefectFromXML/{number},当不提供number时,返回所有的dts数据,当提供number时,返回对应的dts数据或空数据。

2.3           使用方式

2.3.1     第一步:通过spring注入cacheManager这个bean到业务bean中,作为操作cache的类。

<!--spring 继承ehcache -->

<beanid="cacheManager"class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">

<propertyname="configLocation" value="classpath:ehcache.xml" />

 <property name="shared" value="true"/>

</bean>

 

<!-- 配置一个简单的工具类-->

<beanid="dtsCacheManager"class="com.huawei.dts.ehcache.manager.DTSCacheManager">

 <property name="cacheManager"ref="cacheManager" />

</bean>

<!-- 配置DAO BEAN-->

<beanid="xmlIssueDTSManager" class="com.huawei.dts.xml.XMLIssueDTSManager">

 <property name="dtsXMLFileName"value="${dts.xmlfile.name}" />

 <property name="dtsCacheManager"ref="dtsCacheManager" />

</bean>

2.3.2     第二步:给dtsCacheManager编写基本的CRUD API

此处涉及到2点,第一点是如何涉及Cache和Element的组成关系,或者说如何涉及DTS数据存cache的粒度,第二点是如何操作EhCache的API

 a) Cache和Element的关系

Cache是EhCache的管理粒度,也是ehcache.xml中的配置粒度,通过cachename区分

Element是Cache中的数据粒度,一个系统可以有多个Cache,一个Cache可以有多个Element,所以实际应用时需要考虑哪些数据该存cache,哪些数据该存element。

 b) 粒度

每条DTS数据对应一个xml的Element节点,整个xml由多个这样的节点组成。查询操作分为查询单个和查询全部。如果我把每个dts数据都存入一个element,所有dts对应同一个cache,那当查询所有dts数据时就需要遍历所有的element,并拼装返回的数据,效率低下。所以,考虑到效率问题,觉得使用2个cache,第一个用于存放整体数据,里面只有一个element,用于存放整个xml。第二个cache用于存放所有的单个DTS数据的缓存,每个element表示一个被缓存的DTS数据,只要有被缓存过,就会放入其中,element的name使用dts的编号,方便查询。

针对上面的设计,需要考虑一个问题:对于一条被缓存的DTS数据,其实在2个cache中都有备份,如何保证这2个备份之间的数据一致是我们需要关心的问题。规则如下:当整体的cache需要更新时,就把单个的cache清空。当单个数据需要刷新时,需要从数据库或者其他地方获取整份的DTS数据用于遍历得到需要的数据,故可以同步刷新整体的cache。

c) API

首先,配置2个cache到ehcache.xml文件中,每个配置项的具体含义,参看官方文档:

<cachename="xml_dts_cache_all"

        eternal="false"                //是否会失效

        maxElementsInMemory="10000"

        overflowToDisk="true"         //内存超过时,是否保存到磁盘

        maxElementsOnDisk="10000"

        timeToIdleSeconds="30"       //超时时间,30s,后面会验证

        timeToLiveSeconds="30">

</cache>

<cachename="xml_dts_cache_single"

        eternal="false"

        maxElementsInMemory="10000"

        overflowToDisk="true"

        maxElementsOnDisk="10000"

        timeToIdleSeconds="30"

        timeToLiveSeconds="30">

</cache>

查询API:

publicString getFromCache(String cacheName, String elementName)

 {

  Cache c =getCacheManager().getCache(cacheName);

  if(c == null)

  {

   return null;

  }

  net.sf.ehcache.Element e =c.get(elementName);

  if(e == null)

  {

   return null;

  }

  return (String)e.getObjectValue();

 }

更新API:

publicvoid updateToCacheByName(String cacheName, String elementName, StringelementXmlStr)

{

  Element e = parseStrToElement(elementXmlStr);

  net.sf.ehcache.Element element = new net.sf.ehcache.Element(elementName,elementXmlStr);

 getCacheManager().getCache(cacheName).put(element);

  return;

}

清空API:

publicvoid cleanCache(String cacheName)

{

 Cache c =getCacheManager().getCache(cacheName);

 if(c != null)

 {

  c.removeAll();

 }

}

2.3.3     第三步:处理业务逻辑

略…

2.4           验证结果

通过浏览器访问:http://localhost:8080/MyDTSAdapter/dtsrest/query/DefectFromXML

第一次访问:从缓存中取不到,并存入缓存

第二次访问:从缓存中可以去到并返回页面

30秒后再访问:从缓存中取不到,并存入缓存

 http://localhost/MyDTSAdapter/dtsrest/query/DefectFromXML/DTS2014011702577

通过接口查询单个DTS时也一样的效果。

3.      分布式环境下使用EhCache

前面已经实现了在单机环境下使用ehcache,现在我们实现在分布式集群环境下如何使用EhCache。集群环境下的缓存,最需要解决的问题是缓存在各个不同节点间如何共享,这个类似于集群下session的共享一样,session是由tomcat提供的能力,所以其在集群下的共享能力也是有tomcat提供的,同理,ehcache的共享,自然也由EhCache厂商提供。

EhCache支持RMI、JGroups、JMS和Terracotta,本文以RMI为样例进行介绍。不同方案间的本质其实都是一样的,就是把单个节点上产生的缓存,通过广播、远程调用等不同方式,同步到其他节点,使缓存能全局共享。

EhCache通过RMI实现集群,只需要在ehcache中进行少量配置即可,代码级别不需要做任何改动。

3.1         准备工作

部署3个Tomcat,配置Apache,使其支持这3个tomcat的负载均衡,具体可参见http://blog.csdn.net/glgl2424/article/details/23283553此处不再赘述

将MyDTSAdapter这个web服务的ehcache.xml修改好(如何修改请参见下文),打成war包,同时部署到这3个tomcat中。

3.2           集群配置详解

其实,ehcache.xml中自带的配置天然支持集群,只是在配置cache时,需要指明该cache是否需要支持缓存。

为了安装分布式缓存,你需要配置一个PeerProvider、一个CacheManagerPeerListener,它们对于一个CacheManager来说是全局的。每个进行分布式操作的cache都要添加一个cacheEventListener来传送消息,具体如下:

1.cacheManagerPeerProvider  //用于在集群范围内探测和发现新成员,可以支持自动发现,也可以手动配置待发现的成员

2.cacheManagerPeerListener  //消息监听器,用于监听来自其他成员的消息,每个节点一个消息监听器

3.cacheEventListenerFactory  //单个cache的时间监听器。一般用于成员间缓存复制的事件的监听:net.sf.ehcache.distribution.RMICacheReplicatorFactory

4.bootstrapCacheLoaderFactory  //缓存加载引导器,用于对缓存进行初始化。RMI的情况下配置net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory

以上配置,均有更多的子配置项,可以通过参考文章中给的地址进行进一步学习,本文均是使用的默认配置。

<cachename="xml_dts_cache_all"

        eternal="false"

        maxElementsInMemory="10000"

        overflowToDisk="true"

        maxElementsOnDisk="10000"

        timeToIdleSeconds="30"

        timeToLiveSeconds="30">

    <cacheEventListenerFactory

class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"

properties="asynchronousReplicationIntervalMillis=200"/>

    <bootstrapCacheLoaderFactory

class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"/>

</cache>

<cacheManagerPeerProviderFactory

class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"

properties="hostName=127.0.0.1,peerDiscovery=automatic,multicastGroupAddress=230.0.0.1,multicastGroupPort=4446,timeToLive=32"

    propertySeparator=","

/>

<cacheManagerPeerListenerFactoryclass="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"/>

3.3           验证结果

在浏览器输入http://localhost/MyDTSAdapter/dtsrest/query/DefectFromXML/DTS2014011702577

第一次打开:tomcat1打印没有缓存,存入缓存

第二次打开:tomcat2打印已有缓存,从缓存中读取

第三次打开:tomcat3打印已有缓存,从缓存中读取

第四次打开:tomcat1打印已有缓存,从缓存中读取

30秒后打开:tomcat2打印没有缓存,存入缓存

第六次打开:tomcat3打印已有缓存,从缓存中读取

从上述日志中可以发现:第二次打开时,tomcat2中已经有了tomcat1中存入的cache,也就是说cache在不同节点间的同步功能已经生效。在第五次打开时,由于配置了超时,tomcat2重新放入缓存,第六次打开时发现已经从tomcat2拷贝了cache。

4.      参考文章

http://www.blogjava.net/paulwong/archive/2012/02/14/369948.html

http://dreamzhong.iteye.com/blog/1161954

http://www.iteye.com/topic/369725

http://www.ehcache.org/documentation/user-guide/preface

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值