ehcache緩衝實戰__緩衝

來源:互聯網
上載者:User

前面在介紹MyBatis二級緩衝的時候簡單的介紹了ehcache,以及ehcache如何配置為Mybatis的二級緩衝等。這篇文章介紹ehcache在更為上層的應用。
儘快使用ehcache的二級緩衝可以最佳化Mybatis的查詢效率,但這個有幾個限制。

1. 只能在【只有單表操作】的表上使用緩衝

不只是要保證這個表在整個系統中只有單表操作,而且和該表有關的全部操作必須全部在一個namespace下。

2. 在可以保證查詢遠遠大於insert,update,delete操作的情況下使用緩衝

這一點不需要多說,所有人都應該清楚。記住,這一點需要保證在1的前提下才可以。

在實際中其實應該避免使用二級緩衝,mybatis下的二級緩衝具有如下幾點特點:

>*  緩衝是以namespace為單位的,不同namespace下的操作互不影響。>*  insert,update,delete操作會清空所在namespace下的全部緩衝。>*  通常使用MyBatis Generator產生的程式碼中,都是各個表獨立的,每個表都有自己的namespace。

為什麼避免使用二級緩衝

在符合最開始說的二級緩衝的限制時,並沒有什麼影響。

其他情況就會有很多危害了。

針對一個表的某些操作不在他獨立的namespace下進行。

例如在UserMapper.xml中有大多數針對user表的操作。但是在一個XXXMapper.xml中,還有針對user單表的操作。

這會導致user在兩個命名空間下的資料不一致。如果在UserMapper.xml中做了重新整理緩衝的操作,在XXXMapper.xml中緩衝仍然有效,如果有針對user的單表查詢,使用緩衝的結果可能會不正確。

更危險的情況是在XXXMapper.xml做了insert,update,delete操作時(此時會清空所有的緩衝),會導致UserMapper.xml中的各種操作充滿未知和風險。

有關這樣單表的操作可能不常見。但是你也許想到了一種常見的情況。

多表操作一定不能使用緩衝

為什麼不能。

首先不管多表操作寫到那個namespace下,都會存在某個表不在這個namespace下的情況。

例如兩個表:role和user_role,如果我想查詢出某個使用者的全部角色role,就一定會涉及到多表的操作。

<select id="selectUserRoles" resultType="UserRoleVO">    select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid}</select>

像上面這個查詢,你會寫到那個xml中呢。。

不管是寫到RoleMapper.xml還是UserRoleMapper.xml,或者是一個獨立的XxxMapper.xml中。如果使用了二級緩衝,都會導致上面這個查詢結果可能不正確。

如果你正好修改了這個使用者的角色,上面這個查詢使用緩衝的時候結果就是錯的。

這點應該很容易理解。

在我看來,就以MyBatis目前的緩衝方式來看是無解的。多表操作根本不能緩衝。

如果你讓他們都使用同一個namespace(通過<cache-ref>)來避免髒資料,那就失去了緩衝的意義。

挽救二級緩衝。

想更高效率的使用二級緩衝是解決不了了。

但是解決多表操作避免髒資料還是有法解決的。解決思路就是通過攔截器判斷執行的sql涉及到那些表(可以用jsqlparser解析),然後把相關表的緩衝自動清空。但是這種方式對緩衝的使用效率是很低的。

設計這樣一個外掛程式是相當複雜的,既然我沒想著去實現,就不廢話了。

最後還是建議,放棄二級緩衝,在業務層使用可控制的緩衝代替更好。

業務層使用ehcache實戰:

需要添加如下jar包到lib目錄下

ehcache-core-2.5.2.jarehcache-web-2.0.4.jar //主要針對頁面緩衝

3、 當前工程的src目錄中加入設定檔

ehcache.xmlehcache.xsd

這些設定檔在ehcache-core這個jar包中可以找到

Ehcache基本用法

CacheManager cacheManager = CacheManager.create();// 或者cacheManager = CacheManager.getInstance();// 或者cacheManager = CacheManager.create("/config/ehcache.xml");// 或者cacheManager = CacheManager.create("http://localhost:8080/test/ehcache.xml");cacheManager = CacheManager.newInstance("/config/ehcache.xml");// .......// 擷取ehcache設定檔中的一個cacheCache sample = cacheManager.getCache("sample");// 擷取頁面緩衝BlockingCache cache = new BlockingCache(cacheManager.getEhcache("SimplePageCachingFilter"));// 添加資料到緩衝中Element element = new Element("key", "val");sample.put(element);// 擷取緩衝中的對象,注意添加到cache中對象要序列化 實現Serializable介面Element result = sample.get("key");// 刪除緩衝sample.remove("key");sample.removeAll();// 擷取緩衝管理器中的緩衝配置名稱for (String cacheName : cacheManager.getCacheNames()) {    System.out.println(cacheName);}// 擷取所有的緩衝對象for (Object key : cache.getKeys()) {    System.out.println(key);}// 得到緩衝中的對象數cache.getSize();// 得到緩衝對象佔用記憶體的大小cache.getMemoryStoreSize();// 得到緩衝讀取的叫用次數cache.getStatistics().getCacheHits();// 得到緩衝讀取的錯失次數cache.getStatistics().getCacheMisses();

三、頁面緩衝

頁面緩衝主要用Filter過濾器對請求的url進行過濾,如果該url在緩衝中出現。那麼頁面資料就從緩衝對象中擷取,並以gzip壓縮後返回。其速度是沒有壓縮緩衝時速度的3-5倍,效率相當之高。其中頁面緩衝的過濾器有CachingFilter,一般要擴充filter或是自訂Filter都繼承該CachingFilter。
CachingFilter功能可以對HTTP響應的內容進行緩衝。這種方式快取資料的粒度比較粗,例如緩衝整張頁面。它的優點是使用簡單、效率高,缺點是不夠靈活,可重用程度不高。

EHCache使用SimplePageCachingFilter類實現Filter緩衝。該類繼承自CachingFilter,有預設產生cache key的calculateKey()方法,該方法使用HTTP請求的URI和查詢條件來組成key。也可以自己實現一個Filter,同樣繼承CachingFilter類,然後覆寫calculateKey()方法,產生自訂的key。

CachingFilter輸出的資料會根據瀏覽器發送的Accept-Encoding頭資訊進行Gzip壓縮。

在使用Gzip壓縮時,需注意兩個問題: Filter在進行Gzip壓縮時,採用系統預設編碼,對於使用GBK編碼的中文網頁來說,需要將作業系統的語言設定為:zh_CN.GBK,否則會出現亂碼的問題。 預設情況下CachingFilter會根據瀏覽器發送的要求標頭部所包含的Accept-Encoding參數值來判斷是否進行Gzip壓縮。雖然IE6/7瀏覽器是支援Gzip壓縮的,但是在發送請求的時候卻不帶該參數。為了對IE6/7也能進行Gzip壓縮,可以通過繼承CachingFilter,實現自己的Filter,然後在具體的實現中覆寫方法acceptsGzipEncoding。

具體實現參考:

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

ehcache.xml中的配置如下:

<?xml version="1.0" encoding="gbk"?><ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd">    <diskStore path="java.io.tmpdir"/>    <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="false"/>    <!--         配置自訂緩衝        maxElementsInMemory:緩衝中允許建立的最大對象數        eternal:緩衝中對象是否為永久的,如果是,逾時設定將被忽略,對象從不到期。        timeToIdleSeconds:快取資料的鈍化時間,也就是在一個元素消亡之前,                    兩次訪問時間的最大時間間隔值,這隻能在元素不是永久駐留時有效,                    如果該值是 0 就意味著元素可以停頓無窮長的時間。        timeToLiveSeconds:快取資料的存留時間,也就是一個元素從構建到消亡的最大時間間隔值,                    這隻能在元素不是永久駐留時有效,如果該值是0就意味著元素可以停頓無窮長的時間。        overflowToDisk:記憶體不足時,是否啟用磁碟緩衝。        memoryStoreEvictionPolicy:緩衝滿了之後的淘汰演算法。     注意:一個ehcache.xml中不是只能寫一個cache標籤,可以根據緩衝的對象不同而寫多個cache,其他name為其標識位。    -->    <cache name="SimplePageCachingFilter"         maxElementsInMemory="10000"         eternal="false"        overflowToDisk="false"         timeToIdleSeconds="900"         timeToLiveSeconds="1800"        memoryStoreEvictionPolicy="LFU" /></ehcache>

在web.xml中加入如下配置

<!-- ehcache -->    <filter>        <filter-name>SimplePageCachingFilter</filter-name>        <filter-class>net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter        </filter-class>      </filter>  <!-- This is a filter chain. They are executed in the order below.  Do not change the order. -->     <filter-mapping>        <filter-name>SimplePageCachingFilter</filter-name>        <url-pattern>*.do</url-pattern>     </filter-mapping>

注意: ehcache.xml檔案中配置的緩衝名稱 必須和web.xml配置的緩衝filter名字一致 不然會拋出找不到配置的異常 。

當第一次請求這些頁面後,這些頁面就會被添加到緩衝中,以後請求這些頁面將會從緩衝中擷取。你可以在cache.jsp頁面中用小指令碼來測試該頁面是否被緩衝。<%=new Date()%>如果時間是變動的,則表示該頁面沒有被緩衝或是緩衝已經到期,否則則是在緩衝狀態了。

對象緩衝

對象緩衝就是將查詢的資料,添加到緩衝中,下次重新查詢的時候直接從緩衝中擷取,而不去資料庫中查詢。

對象緩衝一般是針對方法、類而來的,結合Spring的Aop對象、方法緩衝就很簡單。這裡需要用到切面編程,用到了Spring的MethodInterceptor或是用@Aspect。

public class MethodCacheInterceptor implements MethodInterceptor, InitializingBean {    private static final Logger log = Logger.getLogger(MethodCacheInterceptor.class);    private Cache cache;    public void setCache(Cache cache) {        this.cache = cache;    }    public void afterPropertiesSet() throws Exception {        log.info(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) {                log.info(cacheKey + "加入到緩衝: " + cache.getName());                // 調用實際的方法                result = invocation.proceed();                element = new Element(cacheKey, (Serializable) result);                cache.put(element);            } else {                log.info(cacheKey + "使用緩衝: " + cache.getName());            }        }        return element.getValue();    }    /**     * <b>function:</b> 返回具體的方法全路徑名稱 參數     * @param targetName 全路徑     * @param methodName 方法名稱     * @param arguments 參數     * @return 完整方法名稱     */    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();    }}

這裡的方法攔截器主要是對你要攔截的類的方法進行攔截,然後判斷該方法的類路徑+方法名稱+參數值組合的cache key在緩衝cache中是否存在。如果存在就從緩衝中取出該對象,轉換成我們要的傳回型別。沒有的話就把該方法返回的對象添加到緩衝中即可。值得主意的是當前方法的參數和傳回值的物件類型需要序列化。

我們需要在src目錄下添加applicationContext.xml完成對MethodCacheInterceptor攔截器的配置,該配置主要是注入我們的cache對象,哪個cache來管理對象緩衝,然後哪些類、方法參與該攔截器的掃描。

<!-- 配置eh緩衝管理器 --><bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/><!-- 配置一個簡單的緩衝工廠bean對象 --><bean id="simpleCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">    <property name="cacheManager" ref="cacheManager" />    <!-- 使用緩衝 關聯ehcache.xml中的緩衝配置 -->    <property name="cacheName" value="mobileCache" /></bean><!-- 配置一個緩衝攔截器對象,處理具體的緩衝業務 --><bean id="methodCacheInterceptor" class="com.common.interceptor.MethodCacheInterceptor">    <property name="cache" ref="simpleCache"/></bean><!-- 參與緩衝的切入點對象 (切入點對象,確定何時何地調用攔截器) --><bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">    <!-- 配置緩衝aop切面 -->    <property name="advice" ref="methodCacheInterceptor" />    <!-- 配置哪些方法參與緩衝策略 -->    <!--          .表示符合任何單一字元                          ###  +表示符合前一個字元一次或多次                          ###  *表示符合前一個字元零次或多次                          ###  \Escape任何Regular expression使用到的符號                      -->                     <!-- .*表示前面的首碼(包括包名) 表示print方法-->    <property name="patterns">        <list>            <value>com.hoo.rest.*RestService*\.*get.*</value>            <value>com.hoo.rest.*RestService*\.*search.*</value>        </list>    </property></bean>

在ehcache.xml中添加如下cache配置

<cache name="mobileCache"        maxElementsInMemory="10000"        eternal="false"        overflowToDisk="true"        timeToIdleSeconds="1800"        timeToLiveSeconds="3600"        memoryStoreEvictionPolicy="LFU" />

參考: http://blog.csdn.net/isea533/article/details/44566257
參考: http://www.cnblogs.com/hoojo/archive/2012/07/12/2587556.html

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.