天天看點

緩存之EhCache學習緩存學習之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