EHCache是來自sourceforge(http://ehcache.sourceforge.net/) 的開源項目,也是純Java實現的簡單、快速的Cache組件。EHCache支持內存和磁盤的緩存,支持LRU、LFU和FIFO多種淘汰算法,支持分 布式的Cache,能夠做爲Hibernate的緩存插件。同時它也能提供基於Filter的Cache,該Filter能夠緩存響應的內容並採用 Gzip壓縮提升響應速度。web
EHCache API的基本用法
首先介紹CacheManager類。它主要負責讀取配置文件,默認讀取CLASSPATH下的ehcache.xml,根據配置文件建立並管理Cache對象。
// 使用默認配置文件建立CacheManager
CacheManager manager = CacheManager.create();
// 經過manager能夠生成指定名稱的Cache對象
Cache cache = cache = manager.getCache("demoCache");
// 使用manager移除指定名稱的Cache對象
manager.removeCache("demoCache");
能夠經過調用manager.removalAll()來移除全部的Cache。經過調用manager的shutdown()方法能夠關閉CacheManager。
有了Cache對象以後就能夠進行一些基本的Cache操做,例如:
//往cache中添加元素
Element element = new Element("key", "value");
cache.put(element);
//從cache中取回元素
Element element = cache.get("key");
element.getValue();
//從Cache中移除一個元素
cache.remove("key");
能夠直接使用上面的API進行數據對象的緩存,這裏須要注意的是對於緩存的對象都是必須可序列化的。在下面的篇幅中筆者還會介紹EHCache和Spring、Hibernate的整合使用。算法
配置文件
配置文件ehcache.xml中命名爲demoCache的緩存配置:spring
<cache name="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配置便可。數據庫
利用Spring APO整合EHCache
首先,在CLASSPATH下面放置ehcache.xml配置文件。在Spring的配置文件中先添加以下cacheManager配置:瀏覽器
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> </bean>
配置demoCache:緩存
<bean id="demoCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean"> <property name="cacheManager" ref="cacheManager" /> <property name="cacheName"> <value>demoCache</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; 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(); } }
synchronized (this)這段代碼實現了同步功能。爲何必定要同步?Cache對象自己的get和put操做是同步的。若是咱們緩存的數據來自數據庫查詢,在沒有這 段同步代碼時,當key不存在或者key對應的對象已通過期時,在多線程併發訪問的狀況下,許多線程都會從新執行該方法,因爲對數據庫進行從新查詢代價是 比較昂貴的,而在瞬間大量的併發查詢,會對數據庫服務器形成很是大的壓力。因此這裏的同步代碼是很重要的。多線程
接下來,繼續完成攔截器和Bean的配置:併發
<bean id="methodCacheInterceptor" class="com.xiebing.utils.interceptor.MethodCacheInterceptor"> <property name="cache"> <ref local="demoCache" /> </property> </bean> <bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref local="methodCacheInterceptor" /> </property> <property name="patterns"> <list> <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>
其中myServiceBean是實現了業務邏輯的Bean,裏面的方法myMethod()的返回結果須要被緩 存。這樣每次對myServiceBean的myMethod()方法進行調用,都會首先從緩存中查找,其次纔會查詢數據庫。使用AOP的方式極大地提升 了系統的靈活性,經過修改配置文件就能夠實現對方法結果的緩存,全部的對Cache的操做都封裝在了攔截器的實現中。app
CachingFilter功能
使用Spring的AOP進行整合,能夠靈活的對方法的的返回結果對象進行緩存。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中,增長下面的配置:
<cache name="SimplePageCachingFilter" maxElementsInMemory="10000" eternal="false"
overflowToDisk="false" timeToIdleSeconds="300" timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU" />
其中name屬性必須爲SimplePageCachingFilter,修改web.xml文件,增長一個Filter的配置:
<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;
}
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"
/>
EHCache的監控
對於Cache的使用,除了功能,在實際的系統運營過程當中,咱們會比較關注每一個Cache對象佔用的內存大小和Cache 的命中率。有了這些數據,咱們就能夠對Cache的配置參數和系統的配置參數進行優化,使系統的性能達到最優。EHCache提供了方便的API供咱們調 用以獲取監控數據,其中主要的方法有:
//獲得緩存中的對象數
cache.getSize();
//獲得緩存對象佔用內存的大小
cache.getMemoryStoreSize();
//獲得緩存讀取的命中次數
cache.getStatistics().getCacheHits()
//獲得緩存讀取的錯失次數
cache.getStatistics().getCacheMisses()
分佈式緩存
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能夠減小網站項目中數據庫服務器的訪問壓力,提升網站的訪問速度, 改善用戶的體驗。