文章出處

在開發高并發量,高性能的網站應用系統時,緩存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壓縮提高響應速度。

 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的緩存配置:

<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的操作都封裝在了攔截器的實現中。

 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是一個非常優秀的基于Java的Cache實現。它簡單、易用,而且功能齊全,并且非常容易與Spring、Hibernate等流行的開源框架進行整合。

通過使用EHCache可以減少網站項目中數據庫服務器的訪問壓力,提高網站的訪問速度,改善用戶的體驗。

 

運城互聯網論壇地址:http://www.dmyc8.com/forum-104-1.html


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()