1. 程式人生 > 實用技巧 >Ehcache 入門詳解

Ehcache 入門詳解

基本介紹

  • EhCache 是一個純Java的程序內快取框架,具有快速、精幹等特點,是Hibernate中預設CacheProvider。Ehcache是一種廣泛使用的開源Java分散式快取。主要面向通用快取,Java EE和輕量級容器。它具有記憶體和磁碟儲存,快取載入器,快取擴充套件,快取異常處理程式,一個gzip快取servlet過濾器,支援REST和SOAP api等特點。
  • Spring 提供了對快取功能的抽象:即允許繫結不同的快取解決方案(如Ehcache),但本身不直接提供快取功能的實現。它支援註解方式使用快取,非常方便。

主要的特性

  1. 快速
  2. 簡單
  3. 多種快取策略
  4. 快取資料有兩級:記憶體和磁碟,因此無需擔心容量問題
  5. 快取資料會在虛擬機器重啟的過程中寫入磁碟
  6. 可以通過RMI、可插入API等方式進行分散式快取
  7. 具有快取和快取管理器的偵聽介面
  8. 支援多快取管理器例項,以及一個例項的多個快取區域
  9. 提供Hibernate的快取實現
  • 可以單獨使用,一般在第三方庫中被用到的比較多(如mybatis、shiro等)ehcache 對分散式支援不夠好,多個節點不能同步,通常和redis一塊使用

ehcache 和 redis 比較

  • ehcache直接在jvm虛擬機器中快取,速度快,效率高;但是快取共享麻煩,叢集分散式應用不方便。
  • redis是通過socket訪問到快取服務,效率比Ehcache低,比資料庫要快很多,處理叢集和分散式快取方便,有成熟的方案。如果是單個應用或者對快取訪問要求很高的應用,用ehcache。如果是大型系統,存在快取共享、分散式部署、快取內容很大的,建議用redis。

ehcache也有快取共享方案,不過是通過RMI或者Jgroup多播方式進行廣播快取通知更新,快取共享複雜,維護不方便;簡單的共享可以,但是涉及到快取恢復,大資料快取,則不合適。

ehcache 2

在pom.xml中引入依賴

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

xml配置

  1. 在src/main/resources/建立一個配置檔案 ehcache.xml

預設情況下Ehcache會自動載入classpath根目錄下名為ehcache.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秒。

java內配置

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

ehcache3

Ehcache 目前提供四層模型,支援四種級別:

  • heap
  • off-heap
  • disk
  • clustered

之前的版本是2級快取,記憶體和磁碟,新版增加了更靈活的一級,堆外快取(off-heap),這既是獨立的程序快取,還是JVM堆外的系統快取 .
提供執行緒池、事務、管理器等,並支援擴充套件和監聽事件,提供了disk持久化以及clustered持久化。

Ehcache3支援堆、堆外、磁碟以及叢集快取;但是除了堆之外外的三種快取,快取的鍵值對必須支援序列化和反序列化。
我們在使用的時候,可以單獨使用任意一個,比如:

  • heap:堆快取,受到jvm的管控,可以不序列化,速度最快;預設使用的是引用傳遞,也可以使用複製器來進行值傳遞。可以設定快取條數或者快取的大小來進行堆快取大小的設定。儘量不要設定的過大,否則容易引起GC.(如果設定快取的大小,則計算比較麻煩,同時又多了一個計算快取大小的過程)。
  • off-heap:堆外快取,不受jvm的管控,受到RAM的限制,在ehcache中配置,至少1M。通過-XX:MaxDirectMemorySize限制ehcache可分配的最大堆外記憶體,但是實際使用發現,不配置也能使用。如果不配置,使用堆外快取時,ehcache將會使用jvm的記憶體,最大值為堆記憶體,但實際比-Xmx要小,可以通過Runtime,getRuntime().maxMemory()獲取。因此,對於堆內對垃圾收集的影響過於嚴重的大量資料,應該選擇堆外。
  • disk:磁碟儲存,儘量使用高效能的SSD。這一層的儲存,不能在不同的CacheManager之間共享!
  • clustered:群集儲存-該資料儲存是遠端伺服器上的快取

層組合

如果要使用多個層,則必須遵守一些約束條件:

  1. 必須始終有堆記憶體
  2. disk和clusterd不能同時存在
  3. 層的大小應採用金字塔式的大小,即,金字塔較高的層配置為使用的記憶體比下方較低的層少。

根據規則可能出現的層組合

  • heap + offheap
  • heap + offheap + disk
  • heap + offheap + clustered
  • heap + disk
  • heap + clustered

多層組合put/get的順序

  • 將值放入快取記憶體時,它會直接進入最低層,比如heap + offheap + disk直接會儲存在disk層。
  • 當獲取一個值,從最高層獲取,如果沒有繼續向下一層獲取,一旦獲取到,會向上層推送,同時上層儲存該值。

訪問模式

Ehcache支援以下幾種模式:

  • Cache-aside
  • Cache-as-SoR
  • Read-through
  • Write-through
  • Write-behind

在pom.xml中引入依賴

<!-- cache 的實現 -->
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.9.0</version>
</dependency>
<!-- cache 的介面 -->
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
</dependency>

java內配置

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder() 
    .withCache("preConfigured",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.newResourcePoolsBuilder()
                                .heap(10, EntryUnit.ENTRIES)
                                .offheap(1, MemoryUnit.MB)
                                .disk(20, MemoryUnit.MB, true))) 
    .build(); 
cacheManager.init(); 
Cache<Long, String> preConfigured =
    cacheManager.getCache("preConfigured", Long.class, String.class); 
Cache<Long, String> myCache = cacheManager.createCache("myCache", 
    CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)));
myCache.put(1L, "da one!"); 
String value = myCache.get(1L); 
cacheManager.removeCache("preConfigured"); 
cacheManager.close(); 

  • CacheManagerBuilder.newCacheManagerBuilder返回一個CacheManagerBuilder例項
  • CacheConfigurationBuilder.newCacheConfigurationBuilder 返回一個CacheConfigurationBuilder例項用於建立CacheConfiguration
  • ResourcePoolsBuilder.newResourcePoolsBuilder返回一個ResourcePoolsBuilder例項用於快取使用可分組,disk持久層設定為true儲存到磁碟
  • 在使用CacheManager,需要對其進行初始化,可以通過2種方式之一進行初始化:CacheManager.init(),或者在CacheManagerBuilder.build(boolean init)
  • cacheManager.close()釋放所有的臨時資源

xml配置

<config
    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xmlns='http://www.ehcache.org/v3'
    xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd">
  <cache alias="foo"> //1
    <key-type>java.lang.String</key-type> //2
    <value-type>java.lang.String</value-type> //2
    <resources>
      <heap unit="entries">20</heap> //3
      <offheap unit="MB">10</offheap> //4
    </resources>
  </cache>
  <cache-template name="myDefaults"> //5
    <key-type>java.lang.Long</key-type>
    <value-type>java.lang.String</value-type>
    <heap unit="entries">200</heap>
  </cache-template>
  <cache alias="bar" uses-template="myDefaults"> //6
    <key-type>java.lang.Number</key-type>
  </cache>
  <cache alias="simpleCache" uses-template="myDefaults" /> //7
</config>
  1. 給Cache別名為foo。
  2. foo的key,value的type定義為String;如果沒有特別定義,預設是java.lang.Object。
  3. foo最多在堆中有2000個entry。
  4. 最多500MB的堆外記憶體。
  5. <cache-template>可以讓你建立一個abstract配置並以後extend它。
  6. 命名為bar的快取用到了命名為myDefaults的並override它的key-type到java.lang.Number。
  7. 命名為simpleCache的快取用myDefaults來作為它的快取配置。

為了解析一個XML配置,你可以用XmlConfiguration:

URL myUrl = getClass().getResource("/my-config.xml"); //1
Configuration xmlConfig = new XmlConfiguration(myUrl); //2
CacheManager myCacheManager = CacheManagerBuilder.newCacheManager(xmlConfig); //3
  1. 新增XML路徑。
  2. 例項化XMLConfiguration。
  3. 用靜態方法org.ehcache.config.builders.CacheManagerBuilder.newCacheManager(org.ehcache.config.Configuration)建立CacheManager例項。

叢集方案下建立cache manager

為了支援Terracotta叢集方案,需要先啟動start the Terracotta server。此外,為了建立叢集方案的cache manager,亦需要提供叢集服務的配置:

CacheManagerBuilder<PersistentCacheManager> clusteredCacheManagerBuilder =
    CacheManagerBuilder.newCacheManagerBuilder() 
        .with(ClusteringServiceConfigurationBuilder.cluster(URI.create("terracotta://localhost/my-application")) 
            .autoCreate(c -> c)); 
PersistentCacheManager cacheManager = clusteredCacheManagerBuilder.build(true); 
 
cacheManager.close(); 

1. 返回org.ehcache.config.builders.CacheManagerBuilder例項。
2. 用靜態方法ClusteringServiceConfigurationBuilder.cluster(URI)來連線對應URI的叢集。例子中的URI指向identifier為my-application的叢集(預設埠號9410);auto-create會在server中的叢集不存在時建立。
3. 返回一個完全初始化的cache manager。
4. 叢集沒有時會自動建立。
5. 關閉cache manager。

Storage Tiers儲存層級

  • Ehcache可以在資料越來越大時,儲存到相對較慢的層級中。
  • 因為快速的儲存資源相對稀有,所以hottest的資源會存在這裡。因此那些較少用到的data會被移動到較慢但儲存容量更大的層級中。更頻繁用到的資料會移動到更快的層級中。

經典的3層級帶硬碟儲存的方案:

PersistentCacheManager persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
    .with(CacheManagerBuilder.persistence(new File(getStoragePath(), "myData"))) //1
    .withCache("threeTieredCache",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
            ResourcePoolsBuilder.newResourcePoolsBuilder()
                .heap(10, EntryUnit.ENTRIES) //2
                .offheap(1, MemoryUnit.MB) //3
                .disk(20, MemoryUnit.MB, true) //4 
            )
    ).build(true);
 
Cache<Long, String> threeTieredCache = persistentCacheManager.getCache("threeTieredCache", Long.class, String.class);
threeTieredCache.put(1L, "stillAvailableAfterRestart"); //5
 
persistentCacheManager.close();

1. 如果你希望使用硬碟儲存,你要提供一個path給CacheManagerBuilder.persistence()方法。
2. 給堆定義一個資源池。這是一個較快但是較小的池。
3. 給堆外記憶體定義一個資源池。這是一個較快單大一點的池。
4. 給硬碟定義一個持久化的資源池。
5. 所有存在cache中的值都可以在JVM重啟後獲得。

Data freshness快取失效

快取失效通過Expiry控制。下面這個例子展示瞭如果控制快取失效:

CacheConfiguration<Long, String> cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
        ResourcePoolsBuilder.heap(100)) //1
    .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(20))) //2
    .build();

1. Expiry在定義cache confuguration的時候配置的。
2. 通過Duration配置time-to-live失效時間。

快取過期和淘汰策略

過期策略

  • no expiry : 永不過期
  • time-to-live :建立後一段時間過期
  • time-to-idle : 訪問後一段時間過期

淘汰策略

  • FIFO
  • LRU 預設策略
  • LFU

java程式碼

CacheConfiguration<Long, String> cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
        ResourcePoolsBuilder.heap(100)) 
    .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(20))) 
    .build();

XML

<cache alias="withExpiry">
  <expiry>
    <ttl unit="seconds">20</ttl> 
  </expiry>
  <heap>100</heap>
</cache>

自定義過期策略

實現ExpiryPolicy介面
介面方法返回值

返回值型別 含義
some Duration 表示將在該持續時間之後過期,
Duration.ZERO 表示立即過期,
Duration.INFINITE 表示對映將永不過期,
null Duration 表示將保留先前的到期時間不變,這在建立時是非法的。

使用自定義過期策略:
java

CacheConfiguration<Long, String> cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
        ResourcePoolsBuilder.heap(100))
    .withExpiry(new CustomExpiry()) 
    .build();

XML

<cache alias="withCustomExpiry">
  <expiry>
    <class>com.pany.ehcache.MyExpiry</class> 
  </expiry>
  <heap>100</heap>
</cache>

序列化

除了堆上的儲存外,所有儲存都需要某種形式的物件序列化/反序列化才能儲存和檢索對映。這是因為它們不能在內部儲存純Java物件,而只能儲存它們的二進位制表示形式。
配置序列化

  • 快取配置級
  • CacheConfigurationBuilder.withKeySerializer(Class<? extends Serializer> keySerializerClass)
  • CacheConfigurationBuilder.withKeySerializer(Serializer keySerializer)
  • CacheConfigurationBuilder.withValueSerializer(Class<? extends Serializer> valueSerializerClass)
  • CacheConfigurationBuilder.withValueSerializer(Serializer valueSerializer)
  • 快取管理器級
  • CacheManagerBuilder.withSerializer(Class clazz, Class<? extends Serializer> serializer)

快取配置優先於快取管理器,即快取配置會覆蓋快取管理器

Ehcache支援以下序列化,按順序處理

  • java.io.Serializable
  • java.lang.Long
  • java.lang.Integer
  • java.lang.Float
  • java.lang.Double
  • java.lang.Character
  • java.lang.String
  • byte[]

自定義序列化

自定義序列化需要實現org.ehcache.spi.serialization.Serializer介面

  • 實現類必須提供一個帶有ClassLoader引數的構造器
  • 實現類必須執行緒安全
  • 被序列化物件的class必須被儲存;被序列化物件的class與反序列化後的物件的class必須相等。(如果在構造CacheManager時沒有指定classLoader,則使用ehcache的預設classLoader)
  • 同時如果實現 java.io.Closeable 介面,當關閉快取管理器時,將呼叫該序列化器的close方法。

使用者管理快取

使用者管理的快取提供了一種直接配置快取的簡單方法,而無需設定或使用CacheManager的複雜性。即快取要求比較簡單可以使用考慮使用者管理快取。

UserManagedCache主要使用:方法本地快取,執行緒本地快取或快取的生命週期短於應用程式生命週期的任何其他地方。

UserManagedCache<Long, String> userManagedCache =
    UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)
        .build(false); 
userManagedCache.init(); 
userManagedCache.put(1L, "da one!"); 
userManagedCache.close(); 

使用者管理持久化快取

如果需要快取持久化可以使用PersistentUserManagedCache.如果要使用磁碟永續性快取,則需要建立永續性服務並對其進行生命週期管理:

LocalPersistenceService persistenceService = new DefaultLocalPersistenceService(new DefaultPersistenceConfiguration(new File(getStoragePath(), "myUserData"))); 

PersistentUserManagedCache<Long, String> cache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Long.class, String.class)
    .with(new UserManagedPersistenceContext<>("cache-name", persistenceService)) 
    .withResourcePools(ResourcePoolsBuilder.newResourcePoolsBuilder()
        .heap(10L, EntryUnit.ENTRIES)
        .disk(10L, MemoryUnit.MB, true)) 
    .build(true);

// Work with the cache
cache.put(42L, "The Answer!");
assertThat(cache.get(42L), is("The Answer!"));

cache.close(); 
cache.destroy(); 

persistenceService.stop(); 

快取監聽

Ehcache 提供 CacheEventListerner 來監聽快取事件。

  • 快取偵聽器允許實現者註冊將在發生快取事件時執行的回撥方法。
  • 監聽器是在快取級別註冊的,因此只接收已註冊的快取的事件。
CacheEventListenerConfigurationBuilder cacheEventListenerConfiguration = CacheEventListenerConfigurationBuilder
    .newEventListenerConfiguration(new ListenerObject(), EventType.CREATED, EventType.UPDATED) 
    .unordered().asynchronous(); 

final CacheManager manager = CacheManagerBuilder.newCacheManagerBuilder()
    .withCache("foo",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(10))
            .add(cacheEventListenerConfiguration) 
    ).build(true);

final Cache<String, String> cache = manager.getCache("foo", String.class, String.class);
cache.put("Hello", "World"); 
cache.put("Hello", "Everyone"); 
cache.remove("Hello"); 
  • CacheEventListenerConfiguration使用構建器建立一個偵聽器和要接收的事件的構建器

在使用快取時,也可以新增和刪除快取事件偵聽器。

ListenerObject listener = new ListenerObject(); 
cache.getRuntimeConfiguration().registerCacheEventListener(listener, EventOrdering.ORDERED,
    EventFiring.ASYNCHRONOUS, EnumSet.of(EventType.CREATED, EventType.REMOVED)); 

cache.put(1L, "one");
cache.put(2L, "two");
cache.remove(1L);
cache.remove(2L);

cache.getRuntimeConfiguration().deregisterCacheEventListener(listener); 

cache.put(1L, "one again");
cache.remove(1L);

事件觸發行為

初始值 操作 新值 事件
{} put(K, V) {K, V} created {K, null, V}
{K, V1} put(K, V2) {K, V2} updated {K, V1, V2}
{} put(K, V) [immediately expired] {} none
{K, V1} put(K, V2) [immediately expired] {} none
{} putIfAbsent(K, V) {K, V} created {K, null, V}
{} putIfAbsent(K, V) [immediately expired] {} none
{K, V1} replace(K, V2) {K, V2} updated {K, V1, V2}
{K, V1} replace(K, V2) [immediately expired] {} none
{K, V1} replace(K, V1, V2) {K, V2} updated {K, V1, V2}
{K, V1} replace(K, V1, V2) [immediately expired] {} no events
{K, V} remove(K) {} removed {K, V, null}

SpringBoot整合ehcache

application.yml配置:

spring:
  cache:
    jcache:
      config: classpath:ehcache.xml

記得加入classpath:表示ehcache.xml在根路徑下

  • @EnableCaching

將@EnableCaching定義在SpringBoot啟動類上,相當於啟用快取功能,內部就會建立 CacheManager(注意是spring框架的), 並交給spring容器管理。

在需要開啟快取的方法上方加上:@Cacheable(cacheNames="快取名(在配置檔案中定義的)",key = 與配置檔案中的value相對(型別) )
注意key=的寫法一般寫字串型別 如:

key = "'dept_' + #deptId" (springEL表示式)

除此之外:@CacheEvict ,寫在需要刪除快取的方法上,用來刪除某個key value,或者清空整個快取, 用在執行了資料的增刪改時,這三種情況下,都應該讓快取失效。

快取執行流程:

  1. 先訪問的是設定快取類的代理物件 (由於cglib 生成該類的子類物件作為代理物件)。
  2. 代理物件重寫了目標方法, 在該方法內, 通過快取管理器(cache)找對應快取名稱的快取。
  3. Cache.get(key)去獲取value, 第一次訪問value為空, 執行的是原本的方法。
  4. 該類的方法返回結果作為value放入快取中。
  5. 第二次訪問的時候,先訪問的是該類的代理物件。
  6. 代理物件重寫了目標方法,方法內部,通過快取管理器(cacheManager)找到對應快取。
  7. Cache.get(key)去獲取對應value, 返回value不為空,直接返回快取中的value, 沒有執行原本的方法,而是執行代理物件中的目標方法。
    快取一般使用在讀多寫少的程式中。