本地緩存Ehcache的應用實踐 | 京東云技術團隊
java本地緩存包含多個框架,其中常用的包括:Caffeine、Guava Cache和Ehcache, 其中Caffeine號稱本地緩存之王,也是近年來被眾多程序員推崇的緩存框架,同時也是SpringBoot內置的本地緩存實現。但是除了Caffeine之外,還有一款也不錯的本地緩存框架Ehcache,具有快速、靈活,并支持內存和磁盤緩存,且提供了豐富的配置選項和緩存策略,接下來一起了解下Ehcache。
一、Ehcache是什么
Ehcache是一個開源的Java本地緩存框架,它提供了快速、靈活的緩存解決方案。Ehcache支持內存和磁盤緩存,并且可以與各種Java應用程序集成,包括基于Spring框架的應用程序。它提供了豐富的配置選項和緩存策略,可以幫助開發人員提高應用程序的性能和響應速度。Ehcache還支持分布式緩存,可以與其他緩存系統集成,如Terracotta集群。
二、Ehcache特點
1、 分層存儲:
堆內存儲: 利用 Java 的堆上 RAM 內存來存儲緩存數據。該層使用與 Java 應用程序相同的堆內存,所有這些內存由 JVM 垃圾收集器掃描(GC)。JVM 使用的堆空間越多,垃圾收集暫停對應用程序性能的影響就越大。該存儲速度非常快,但通常資源有限。
堆外存儲: 大小受可用 RAM 的限制。不受 Java 垃圾收集 (GC) 的影響。速度相當快,但比堆上存儲慢,因為在存儲和重新訪問數據時必須將數據移入和移出 JVM 堆。
磁盤存儲: 利用磁盤(文件系統)來存儲緩存數據。這種類型的存儲資源通常非常豐富,但比基于 RAM 的存儲慢一些。對于所有使用磁盤存儲的應用程序,建議使用快速且專用的SSD磁盤來優化吞吐量。
集群存儲(分布式): 此數據存儲是遠程服務器上的緩存。由于網絡延遲以及建立客戶端/服務器一致性等因素,集群存儲會帶來額外的性能損耗,性能上會更低一些。
2、 靈活有效期:
沒有期限:緩存映射在應用存在下永遠不會過期;
生存周期:緩存映射將在創建后的固定時間后過期;
空閑時間:緩存映射將在上次訪問后的固定持續時間后過期;
定制有效期:通過重載ExpiryPolicy接口實現個性化的過期判斷;
接口如下:

返回值定義:
Duration.ZERO: 表示立即過期
Duration.INFINITE: 映射永遠不會過期
Duration.設置具體時間: 設置對應的時間后過期
Duration.設置時間周期: 設置對應的周期后過期
Duration設置null:之前的過期時間保持不變
3、淘汰策略
LFU:訪問頻率值小的緩存淘汰。
LRU:基于最近訪問時間來進行淘汰。
FIFO:數據項最早進入緩存的時間來進行淘汰。
三、緩存原理
以三層為例:堆內存,堆外存儲,本地磁盤存儲。
架構圖:

說明:cache-manager、cache、element為Ehcache本地緩存的核心,通過數據寫入的事務操作保證個層間的一致性。同時基于存儲變更監聽程序,針對變更的數據以及滿足淘汰策略數據進行清理,亦或持久化至本地磁盤;
流程圖(基于源碼整理):
待補充
四、實際應用
1、pom引入:
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.0</version>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
2、創建實例:
/*************************** 1.純內存操作 *****************************/
// 1.1 創建緩存 preConfigured 基于 內存
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("preConfigured",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)))
.build(true);
// 1.2 獲取緩存實例
Cache<Long, String> preConfigured = cacheManager.getCache("preConfigured", Long.class, String.class);
/*************************** 2.新增實例 *****************************/
// 2.1 創建新的實例 并獲取實例
Cache<Long, String> myCache = cacheManager.createCache("myCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)));
/*************************** 3.三層存儲-持久化磁盤 *****************************/
// 3.1 創建緩存 myData 基于 內存->堆外存儲->本地磁盤
PersistentCacheManager persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(CacheManagerBuilder.persistence(new File("/home/export/App/conf/", "myData")))
.withCache("threeTieredCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(1, MemoryUnit.MB)
.disk(20, MemoryUnit.MB, true)
)
).build(true);
// 3.2 獲取存儲實例 threeTieredCache
Cache<Long, String> threeTieredCache = persistentCacheManager.getCache("threeTieredCache", Long.class, String.class);
/*************************** 4.一個manager管理多個緩存 - 持久化磁盤 *****************************/
// 4.1 一個實例管理多個緩存 并且每個緩存都可持久化到本地磁盤
PersistentCacheManager persistentCacheManager1 = CacheManagerBuilder.newCacheManagerBuilder()
.with(CacheManagerBuilder.persistence(
new File("/path/to/persistent/directory1").getAbsoluteFile()))
.withCache("cache1",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(1, MemoryUnit.MB)
.disk(20, MemoryUnit.MB, true)
)
)
.with(CacheManagerBuilder.persistence(
new File("/path/to/persistent/directory2").getAbsoluteFile()))
.withCache("cache2",
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Integer.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(20, EntryUnit.ENTRIES)
.offheap(2, MemoryUnit.MB)
.disk(30, MemoryUnit.MB, true)
)
)
.build(true);
說明:
a. 上述常見緩存實例的方法有多個,其中第一種為純內存操作,第三種為三層存儲并持久化磁盤實例,下面以第三種方式進行測試驗證;
日常應用可以進行組合使用,例如:
b. 如果選擇本地磁盤存儲(系統退出前需要使用persistentCacheManager.close();釋放資源,方可保證磁盤數據準確),后續系統重啟后會加載磁盤內數據至緩存中,使得緩存在有效期內依然有效,可減少應用啟動對后端DB的壓力;
3、用例

4、結果:

持久化磁盤:


5、結論:
緩存+磁盤測試OK;
有效期數據,失效后不返回;
系統重啟加載磁盤數據正常;
??其他說明:
Ehcache可結合Terracotta插件實現分布式存儲,對該部分感興趣的同學可一起探討。但是對于線上系統而言,若需分布式存儲,建議直接使用redis。Ehcache的分布式實現并不可靠,核心還是采用廣播集群方式,實現數據的同步及更新,并且性能受機器IO,磁盤,網絡等影響,在實際應用情況下,不會比redis更好。
6、以下為完成測試代碼
package com.jx.jxreserve.groupbuy.manager;
import com.jd.flash.commons.exception.BaseBusinessException;
import com.jx.jxreserve.groupbuy.common.enums.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.PersistentCacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.EntryUnit;
import org.ehcache.config.units.MemoryUnit;
import org.springframework.stereotype.Component;
import java.io.File;
import java.time.Duration;
import static org.ehcache.Status.AVAILABLE;
/**
* @author zhangpengfei9
* @version V1.0
* Ehcache的測試管理類
*/
@Slf4j
@Component
public class EhcacheTestManager {
/*************************** 1.純內存操作 *****************************/
// 1.1 創建緩存 preConfigured 基于 內存
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("preConfigured",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)))
.build(true);
// 1.2 獲取緩存實例
Cache<Long, String> preConfigured = cacheManager.getCache("preConfigured", Long.class, String.class);
/*************************** 2.新增實例 *****************************/
// 2.1 創建新的實例 并獲取實例
Cache<Long, String> myCache = cacheManager.createCache("myCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)));
/*************************** 3.三層存儲 *****************************/
// 3.1 創建緩存 myData 基于 內存->堆外存儲->本地磁盤
PersistentCacheManager persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(CacheManagerBuilder.persistence(new File("/home/export/App/conf/", "groupData")))
.withCache("testDiskCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(1, MemoryUnit.MB)
.disk(20, MemoryUnit.MB, true)
)
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(600))) // 設置緩存有效期
).build(true);
/*************************** 4.多個緩存 - 三層存儲 *****************************/
// 4.1 一個實例管理多個緩存 并且每個緩存都可持久化到本地磁盤
PersistentCacheManager persistentCacheManager1 = CacheManagerBuilder.newCacheManagerBuilder()
.with(CacheManagerBuilder.persistence(
new File("/home/export/App/conf/").getAbsoluteFile()))
.withCache("cache1",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(1, MemoryUnit.MB)
.disk(20, MemoryUnit.MB, true)
)
)
.with(CacheManagerBuilder.persistence(
new File("/home/export/App/conf/").getAbsoluteFile()))
.withCache("cache2",
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Integer.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(20, EntryUnit.ENTRIES)
.offheap(2, MemoryUnit.MB)
.disk(30, MemoryUnit.MB, true)
)
)
.build(true);
/**
* 設置緩存
*/
public void setEhCache(Long key, String values) throws BaseBusinessException {
try {
// 獲取存儲實例 threeTieredCache
log.info("setEhCache.value:{},{}", values, key);
Cache<Long, String> testDiskCache = getManagerCache("testDiskCache");
testDiskCache.put(key, values);
} catch (Exception e) {
log.error("setEhCache failure! Exception:", e);
throw new BaseBusinessException(ErrorCode.SYSTEM_DB_ERROR.getCode(), ErrorCode.SYSTEM_DB_ERROR.getMessage());
}
}
/**
* 查詢緩存
*/
public String getEhCache(Long key) throws BaseBusinessException {
try {
// 獲取存儲實例 threeTieredCache
log.info("getEhCache.key:{}", key);
Cache<Long, String> testDiskCache = getManagerCache("testDiskCache");
return testDiskCache.get(key);
} catch (Exception e) {
log.error("setEhCache failure! Exception:", e);
throw new BaseBusinessException(ErrorCode.SYSTEM_DB_ERROR.getCode(), ErrorCode.SYSTEM_DB_ERROR.getMessage());
}
}
/**
* 設置緩存
*/
public void closeEhCache() throws BaseBusinessException {
try {
// 獲取存儲實例 threeTieredCache
log.info("closeEhCache.persistentCacheManager.close1:{}", persistentCacheManager.getStatus());
persistentCacheManager.close();
log.info("closeEhCache.persistentCacheManager.close2:{}", persistentCacheManager.getStatus());
} catch (Exception e) {
log.error("setEhCache failure! Exception:", e);
throw new BaseBusinessException(ErrorCode.SYSTEM_DB_ERROR.getCode(), ErrorCode.SYSTEM_DB_ERROR.getMessage());
}
}
private Cache<Long, String> getManagerCache(String cache) {
// 3.1 創建緩存 myData 基于 內存->堆外存儲->本地磁盤
log.info("persistentCacheManager.getStatus():{}", persistentCacheManager.getStatus());
if (AVAILABLE != persistentCacheManager.getStatus()) {
persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(CacheManagerBuilder.persistence(new File("/home/export/App/conf/", "groupData")))
.withCache("testDiskCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(1, MemoryUnit.MB)
.disk(20, MemoryUnit.MB, true)
)
).build(true);
log.info("persistentCacheManager.getStatus1:{}", persistentCacheManager.getStatus());
}
Cache<Long, String> testDiskCache = persistentCacheManager.getCache(cache, Long.class, String.class);
return testDiskCache;
}
}
作者:京東零售 張鵬飛
來源:京東云開發者社區 轉載請注明來源
浙公網安備 33010602011771號