Ehcache緩存實例

一:目錄

  • EhCache 簡介
  • Hello World 示例
  • Spring 整合
  • Dummy CacheManager 的配置和做用java

二: 簡介

1. 基本介紹

EhCache 是一個純Java的進程內緩存框架,具備快速、精幹等特色,是Hibernate中默認CacheProvider。Ehcache是一種普遍使用的開源Java分佈式緩存。主要面向通用緩存,Java EE和輕量級容器。它具備內存和磁盤存儲,緩存加載器,緩存擴展,緩存異常處理程序,一個gzip緩存servlet過濾器,支持REST和SOAP api等特色。redis

Spring 提供了對緩存功能的抽象:即容許綁定不一樣的緩存解決方案(如Ehcache),但自己不直接提供緩存功能的實現。它支持註解方式使用緩存,很是方便。算法

2. 主要的特性有:

  1. 快速
  2. 簡單
  3. 多種緩存策略
  4. 緩存數據有兩級:內存和磁盤,所以無需擔憂容量問題
  5. 緩存數據會在虛擬機重啓的過程當中寫入磁盤
  6. 能夠經過RMI、可插入API等方式進行分佈式緩存
  7. 具備緩存和緩存管理器的偵聽接口
  8. 支持多緩存管理器實例,以及一個實例的多個緩存區域
  9. 提供Hibernate的緩存實現

3. 集成

能夠單獨使用,通常在第三方庫中被用到的比較多(如mybatis、shiro等)ehcache 對分佈式支持不夠好,多個節點不能同步,一般和redis一塊使用spring

4. ehcache 和 redis 比較

  • ehcache直接在jvm虛擬機中緩存,速度快,效率高;可是緩存共享麻煩,集羣分佈式應用不方便。數據庫

  • redis是經過socket訪問到緩存服務,效率比ecache低,比數據庫要快不少, 
    處理集羣和分佈式緩存方便,有成熟的方案。若是是單個應用或者對緩存訪問要求很高的應用,用ehcache。若是是大型系統,存在緩存共享、分佈式部署、緩存內容很大的,建議用redis。編程

ehcache也有緩存共享方案,不過是經過RMI或者Jgroup多播方式進行廣播緩存通知更新,緩存共享複雜,維護不方便;簡單的共享能夠,可是涉及到緩存恢復,大數據緩存,則不合適。api

緩存命中率

即從緩存中讀取數據的次數 與 總讀取次數的比率,命中率越高越好:緩存

命中率 = 從緩存中讀取次數 / (總讀取次數[從緩存中讀取次數 + 從慢速設備上讀取的次數])mybatis

Miss率 = 沒有從緩存中讀取的次數 / (總讀取次數[從緩存中讀取次數 + 從慢速設備上讀取的次數])app

 

這是一個很是重要的監控指標,若是作緩存必定要健康這個指標來看緩存是否工做良好;

 

緩存策略

Eviction policy

移除策略,即若是緩存滿了,從緩存中移除數據的策略;常見的有LFU、LRU、FIFO:

FIFO(First In First Out):先進先出算法,即先放入緩存的先被移除;

LRU(Least Recently Used):最久未使用算法,使用時間距離如今最久的那個被移除;

LFU(Least Frequently Used):最近最少使用算法,必定時間段內使用次數(頻率)最少的那個被移除;

 

TTL(Time To Live )

存活期,即從緩存中建立時間點開始直到它到期的一個時間段(無論在這個時間段內有沒有訪問都將過時)

 

TTI(Time To Idle)

空閒期,即一個數據多久沒被訪問將從緩存中移除的時間。

 

Cache接口默認提供了以下實現:

ConcurrentMapCache:使用java.util.concurrent.ConcurrentHashMap實現的Cache;

GuavaCache:對Guava com.google.common.cache.Cache進行的Wrapper,須要Google Guava 12.0或更高版本,@since spring 4;

EhCacheCache:使用Ehcache實現

JCacheCache:對javax.cache.Cache進行的wrapper,@since spring 3.2;spring4將此類更新到JCache 0.11版本;


三: Hello World

一、在pom.xml中引入依賴

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.2</version>
</dependency>

 

二、在src/main/resources/建立一個配置文件 ehcache.xml

默認狀況下Ehcache會自動加載classpath根目錄下名爲ehcache.xml文件,也能夠將該文件放到其餘地方在使用時指定文件的位置

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!--表示硬盤上保存緩存的位置。默認是臨時文件夾。-->
    <!--<diskStore path="java.io.tmpdir"/>-->
    <diskStore path="/logs/ehcache"/>
    <!--默認緩存配置,若是類沒有作特定的設置,則使用這裏配置的緩存屬性。
       maxElementsInMemory  - 設置緩存中容許保存的最大對象(pojo)數量
       eternal -設置對象是否永久保存,若是爲true,則緩存中的數據永遠不銷燬,一直保存。
       timeToIdleSeconds - 設置空閒銷燬時間。只有eternal爲false時才起做用。表示從如今到上次訪問時間若是超過這個值,則緩存數據銷燬
       timeToLiveSeconds-設置活動銷燬時間。表示從如今到緩存建立時間若是超過這個值,則緩存自動銷燬
       overflowToDisk - 設置是否在超過保存數量時,將超出的部分保存到硬盤上。-->
    <defaultCache
            maxElementsInMemory="1500"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="300"
            overflowToDisk="true"/>
 
    <cache name="demoCache"
           maxElementsInMemory="1000"
           eternal="true"
           timeToIdleSeconds="0"
           timeToLiveSeconds="0"
           overflowToDisk="true"/>

<!-- helloworld緩存 -->
<cache name="HelloWorldCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="5"
timeToLiveSeconds="5"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>

<cache name="UserCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>

三、測試類

public class MyEhcache {
    public static void main(String[] args) {
        User user=new User("cache","123456","cache cache","","male",20); 

        // 獲取緩存管理器
        CacheManager manager = CacheManager.create("src/main/resources/config/ehcache.xml");
       // 根據配置文件得到Cache實例
        Cache demo = manager.getCache("demoCache");

        // 清空Cache中的全部元素
        demo.removeAll();
        demo.put(new Element("hello","world"));
        demo.put(new Element(user.getUserName(),user)); 


        Element e=demo.get(user.getUserName()); 

        System.out.println(demo.get("hello").getObjectValue());
        System.out.println(((User)e.getObjectValue()).getUserRealName()); 

        //卸載緩存管理器
       // manager.shutdown();
    }
}

四、緩存配置

一:xml配置方式:

  • diskStore : ehcache支持內存和磁盤兩種存儲

    • path :指定磁盤存儲的位置
  • defaultCache : 默認的緩存

    • maxEntriesLocalHeap=」10000」
    • eternal=」false」
    • timeToIdleSeconds=」120」
    • timeToLiveSeconds=」120」
    • maxEntriesLocalDisk=」10000000」
    • diskExpiryThreadIntervalSeconds=」120」
    • memoryStoreEvictionPolicy=」LRU」
  • cache :自定的緩存,當自定的配置不知足實際狀況時能夠經過自定義(能夠包含多個cache節點)

    • name : 緩存的名稱,能夠經過指定名稱獲取指定的某個Cache對象

    • maxElementsInMemory :內存中容許存儲的最大的元素個數,0表明無限個

    • clearOnFlush:內存數量最大時是否清除。

    • eternal :設置緩存中對象是否爲永久的,若是是,超時設置將被忽略,對象從不過時。根據存儲數據的不一樣,例如一些靜態不變的數據如省市區等能夠設置爲永不過期

    • timeToIdleSeconds : 設置對象在失效前的容許閒置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閒置時間無窮大。

    • timeToLiveSeconds :緩存數據的生存時間(TTL),也就是一個元素從構建到消亡的最大時間間隔值,這隻能在元素不是永久駐留時有效,若是該值是0就意味着元素能夠停頓無窮長的時間。

    • overflowToDisk :內存不足時,是否啓用磁盤緩存。

    • maxEntriesLocalDisk:當內存中對象數量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中。

    • maxElementsOnDisk:硬盤最大緩存個數。

    • diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每一個Cache都應該有本身的一個緩衝區。

    • diskPersistent:是否在VM重啓時存儲硬盤的緩存數據。默認值是false。

    • diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。

二:編程方式配置

Cache cache = manager.getCache("mycache"); 
CacheConfiguration config = cache.getCacheConfiguration(); 
config.setTimeToIdleSeconds(60); 
config.setTimeToLiveSeconds(120); 
config.setmaxEntriesLocalHeap(10000); 
config.setmaxEntriesLocalDisk(1000000);

五、Ehcache API

  • CacheManager:Cache的容器對象,並管理着(添加或刪除)Cache的生命週期。
// 能夠本身建立一個Cache對象添加到CacheManager中
public void addCache(Cache cache);
public synchronized void removeCache(String cacheName);

 

  • Cache: 一個Cache能夠包含多個Element,並被CacheManager管理。它實現了對緩存的邏輯行爲

  • Element:須要緩存的元素,它維護着一個鍵值對, 元素也能夠設置有效期,0表明無限制

  • 獲取CacheManager的方式:

    能夠經過create()或者newInstance()方法或重載方法來建立獲取CacheManager的方式:

public static CacheManager create();
public static CacheManager create(String configurationFileName);
public static CacheManager create(InputStream inputStream);
public static CacheManager create(URL configurationFileURL);

public static CacheManager newInstance();

 

Ehcache的CacheManager構造函數或工廠方法被調用時,會默認加載classpath下名爲ehcache.xml的配置文件。 
若是加載失敗,會加載Ehcache jar包中的ehcache-failsafe.xml文件,這個文件中含有簡單的默認配置。

// CacheManager.create() == CacheManager.create("./src/main/resources/ehcache.xml")
// 使用Ehcache默認配置新建一個CacheManager實例
CacheManager cacheManager = CacheManager.create();
cacheManager = CacheManager.newInstance();

cacheManager = CacheManager.newInstance("./src/main/resources/ehcache.xml");

InputStream inputStream = new FileInputStream(new File("./src/main/resources/ehcache.xml"));
cacheManager = CacheManager.newInstance(inputStream);

String[] cacheNames = cacheManager.getCacheNames();  // [HelloWorldCache]

 


四:Spring整合

一、配置spring-ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/cache
        http://www.springframework.org/schema/cache/spring-cache-3.2.xsd">

  <description>ehcache緩存配置管理文件</description>
<!-- 啓用緩存註解開關,這個是必須的,不然註解不會生效。另外,該註解必定要聲明在spring主配置文件中才會生效 -->
<!-- 自定義CacheKeyGenerator,當緩存沒有定義key的時候有效-->
<cache:annotation-driven cache-manager="cacheManager" key-generator="cacheKeyGenerator"/>
<bean id="cacheKeyGenerator" class="com.esther.code.util.CacheKeyGenerator"/>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> 

<property name="cacheManager" ref="ehcache"/> </bean>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml"/>
</bean>

</beans>

 自定義緩存MyCacheable: 

import org.springframework.cache.annotation.Cacheable;

import java.lang.annotation.*;

/**
 * @author  
 * 2018-04-23 18:38
 * $DESCRIPTION}
 */
@Retention(RetentionPolicy.RUNTIME)//註解會在class中存在,運行時可經過反射獲取
@Target({ElementType.METHOD,ElementType.TYPE})//目標是方法
@Documented//文檔生成時,該註解將被包含在javadoc中,可去掉
@Cacheable(value = "UserCache")
public @interface MyCacheable {
}

CacheKeyGenerator :

import com.google.common.hash.Hashing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.nio.charset.Charset;

/**
 * @author 
 * 2018-04-23 16:01
 * $DESCRIPTION}
 */
@Component("cacheKeyGenerator ")
public class CacheKeyGenerator implements KeyGenerator {
    private Logger log= LoggerFactory.getLogger(getClass());

    // custom cache key
    public static final int NO_PARAM_KEY = 0;
    public static final int NULL_PARAM_KEY = 53;

    @Override
    public Object generate(Object target, Method method, Object... params) {

        StringBuilder key = new StringBuilder();
        key.append(target.getClass().getSimpleName()).append(".").append(method.getName()).append("(");
        if (params.length == 0) {
            return key.append(NO_PARAM_KEY).toString();
        }
        for (Object param : params) {
            if (param == null) {
                log.warn("input null param for Spring cache, use default key={}", NULL_PARAM_KEY);
                key.append(NULL_PARAM_KEY);
            } else if (ClassUtils.isPrimitiveArray(param.getClass())) {
                int length = Array.getLength(param);
                for (int i = 0; i < length; i++) {
                    key.append(Array.get(param, i));
                    key.append('|');
                }
            } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
                key.append(param);
            } else {
                log.warn("Using an object as a cache key may lead to unexpected results. " +
                        "Either use @Cacheable(key=..) or implement CacheKey. Method is " + target.getClass() + "#" + method.getName());
                key.append(param.hashCode());
            }
            key.append(',');
        }
        // 去除最後一個逗號
        key.deleteCharAt(key.length()-1);
        key.append(")");

        String finalKey = key.toString();
        long cacheKeyHash = Hashing.murmur3_128().hashString(finalKey, Charset.defaultCharset()).asLong();
        log.debug("using cache key={}    hashCode={}", finalKey, cacheKeyHash);
        return key.toString();
    }
}

測試接口和實現類:

public interface IEhcacheService {

    // 測試失效狀況,有效期爲5秒
    public String getTimestamp(String param);

    public String getDataFromDB(String key);

    public void removeDataAtDB(String key);

    public String refreshData(String key);


    public User findUserById(Integer userId);

    public boolean isReserved(Integer userId);

    public void removeUser(Integer userId);

    public void removeAllUser();

    // @Caching註解實現
    public String testCaching(String param);

    // 自定義註解實現
    User get(Integer userId);
}

 

import com.esther.code.annotation.MyCacheable;
import com.esther.code.api.IEhcacheService;
import com.esther.code.api.IUserService;
import com.esther.code.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;

/**
 * @author 
 * 2018-04-23 10:59
 * ehcache與spring註解實現
 */
@Service("ehcacheService")
public class EhcacheServiceImpl implements IEhcacheService {

    @Autowired
    private IUserService userService;

    // value的值和ehcache.xml中的配置保持一致
    @Cacheable(value = "HelloWorldCache", key = "#param")
    @Override
    public String getTimestamp(String param) {
        Long timestamp = System.currentTimeMillis();
        return timestamp.toString();
    }

    @Cacheable(value = "HelloWorldCache", key = "#key")
    @Override
    public String getDataFromDB(String key) {
        System.out.println("從數據庫中獲取數據...");
        return key + ":" + String.valueOf(Math.round(Math.random() * 1000000));
    }

    @CacheEvict(value = "HelloWorldCache", key = "#key")
    @Override
    public void removeDataAtDB(String key) {
        System.out.println("從數據庫中刪除數據");
    }

    // 使用@CachePut標註的方法在執行前不會去檢查緩存中是否存在以前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。
   // 每次都會執行方法,並將結果存入指定的緩存中
    @CachePut(value = "HelloWorldCache", key = "#key")
    @Override
    public String refreshData(String key) {
        System.out.println("模擬從數據庫中加載數據");
        return key + "::" + String.valueOf(Math.round(Math.random()*1000000));
    }

    // ------------------------------------------------------------------------

    /**
     * 沒有定義key的話,使用xml文件中配置的CacheKeyGenerator
     *
     * unless過濾方法返回值。當方法返回空值時,就不會被緩存起來
     * condition:對傳入的參數進行篩選. 觸發條件,只有知足條件的狀況纔會加入緩存,默認爲空,既表示所有都加入緩存,支持SpEL。
     * expire:過時時間,單位爲秒。
     * beforeInvocation: 是否在方法執行前就清空,缺省爲 false,若是指定爲 true,則在方法尚未執行的時候就清空緩存,缺省狀況下,若是方法執行拋出異常,則不會清空緩存
     * @param userId
     * @return
     */
    @Override
    @Cacheable(value = "UserCache", condition = "#userId<=2", unless = "#result==null")
    public User findUserById(Integer userId) {
        System.out.println("模擬從數據庫中查詢數據");
        return userService.selectByPrimaryKey(userId);
    }

    /**
     * condition:對傳入的參數進行篩選. 觸發條件,只有知足條件的狀況纔會加入緩存,默認爲空,既表示所有都加入緩存,支持SpEL。
     * expire:過時時間,單位爲秒。
     * @param userId
     * @return
     */
    @Override
    @Cacheable(value = "UserCache", condition = "#userId<=2")
    public boolean isReserved(Integer userId) {
        System.out.println("UserCache:" + userId);
        return false;
    }

    //清除掉UserCache中某個指定key的緩存
    @Override
    @CacheEvict(value = "UserCache", key = "'user:' + #userId")
    public void removeUser(Integer userId) {
        System.out.println("UserCache remove:" + userId);
    }

    //清除掉UserCache中所有的緩存
    @Override
    @CacheEvict(value = "UserCache", allEntries = true)
    public void removeAllUser() {
        System.out.println("UserCache delete all");
    }

    /**
     * 多個緩存組合的@Caching
     * @param param
     * @return
     */
    @Override
    @Caching(evict = @CacheEvict(value = "UserCache",allEntries=true),cacheable = {@Cacheable("HelloWorldCache")})
    public String testCaching(String param){
        System.out.println("UserCache delete all");
        Long timestamp = System.currentTimeMillis();
        return timestamp.toString();
    }

    /**
     * 自定義緩存MyCacheable
     * @param userId
     * @return
     */
    @Override
    @MyCacheable
    public User get(Integer userId) {
        return userService.selectByPrimaryKey(userId);
    }
}

 5、Dummy CacheManager 的配置和做用

  有的時候,咱們在代碼遷移、調試或者部署的時候,剛好沒有 cache 容器,好比 memcache 還不具有條件,h2db 尚未裝好等,若是這個時候你想調試代碼,豈不是要瘋掉?這裏有一個辦法,在不具有緩存條件的時候,在不改代碼的狀況下,禁用緩存。

方法就是修改 spring*.xml 配置文件,設置一個找不到緩存就不作任何操做的標誌位,以下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/cache
        http://www.springframework.org/schema/cache/spring-cache-3.2.xsd">

    <description>ehcache緩存配置管理文件</description>

    <!-- 啓用緩存註解開關,這個是必須的,不然註解不會生效。另外,該註解必定要聲明在spring主配置文件中才會生效 -->
    <!-- 自定義CacheKeyGenerator,當緩存沒有定義key的時候有效-->
<!--mode屬性,可選值有proxy和aspectj。默認是使用proxy。
當mode爲proxy時,只有緩存方法在外部被調用的時候Spring Cache纔會發生做用,
這也就意味着若是一個緩存方法在其聲明對象內部被調用時Spring Cache是不會發生做用的。而mode爲aspectj時就不會有這種問題。
另外使用proxy時,只有public方法上的@Cacheable等標註纔會起做用,若是須要非public方法上的方法也可使用Spring Cache時把mode設置爲aspectj。

此外,<cache:annotation-driven/>還能夠指定一個proxy-target-class屬性,表示是否要代理class,默認爲false。
咱們前面提到的@Cacheable、@cacheEvict等也能夠標註在接口上,這對於基於接口的代理來講是沒有什麼問題的,
可是須要注意的是當咱們設置proxy-target-class爲true或者mode爲aspectj時,是直接基於class進行操做的,
定義在接口上的@Cacheable等Cache註解不會被識別到,那對應的Spring Cache也不會起做用了。-->
    <cache:annotation-driven cache-manager="cacheManager" key-generator="cacheKeyGenerator"/>
    <bean id="cacheKeyGenerator" class="com.esther.code.util.CacheKeyGenerator"/>

    <!--通常的緩存管理器,如EhCache-->
    <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="ehcache"/>
    </bean>

    <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache.xml"/>
    </bean>

    <!--Dummy CacheManager:在找不到對應緩存時(如UserCache),能夠設置標誌位fallbackToNoOpCache=true,禁用緩存。
    若是找不到對應緩存,且fallbackToNoOpCache=false,拋異常java.lang.IllegalArgumentException: Cannot find cache named 'UserCache' for CacheEvictOperation-->
    <bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
        <property name="cacheManagers">
            <list>
                <ref bean="ehcacheManager"/>
            </list>
        </property>
        <property name="fallbackToNoOpCache" value="true"/>
    </bean>
</beans>
相關文章
相關標籤/搜索