基本介紹
- EhCache 是一個純Java的進程內緩存框架,具有快速、精干等特點,是Hibernate中默認CacheProvider。Ehcache是一種廣泛使用的開源Java分布式緩存。主要面向通用緩存,Java EE和輕量級容器。它具有內存和磁盤存儲,緩存加載器,緩存擴展,緩存異常處理程序,一個gzip緩存servlet過濾器,支持REST和SOAP api等特點。
- Spring 提供了對緩存功能的抽象:即允許綁定不同的緩存解決方案(如Ehcache),但本身不直接提供緩存功能的實現。它支持注解方式使用緩存,非常方便。
主要的特性
- 快速
- 簡單
- 多種緩存策略
- 緩存數據有兩級:內存和磁盤,因此無需擔心容量問題
- 緩存數據會在虛擬機重啟的過程中寫入磁盤
- 可以通過RMI、可插入API等方式進行分布式緩存
- 具有緩存和緩存管理器的偵聽接口
- 支持多緩存管理器實例,以及一個實例的多個緩存區域
- 提供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配置
- 在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:群集存儲-該數據存儲是遠程服務器上的緩存
層組合
如果要使用多個層,則必須遵守一些約束條件:
- 必須始終有堆內存
- disk和clusterd不能同時存在
- 層的大小應采用金字塔式的大小,即,金字塔較高的層配置為使用的內存比下方較低的層少。
根據規則可能出現的層組合
- 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>
- 給Cache別名為foo。
- foo的key,value的type定義為String;如果沒有特別定義,默認是java.lang.Object。
- foo最多在堆中有2000個entry。
- 最多500MB的堆外內存。
- <cache-template>可以讓你創建一個abstract配置並以后extend它。
- 命名為bar的緩存用到了命名為myDefaults的
並override它的key-type到java.lang.Number。 - 命名為simpleCache的緩存用myDefaults來作為它的緩存配置。
為了解析一個XML配置,你可以用XmlConfiguration:
URL myUrl = getClass().getResource("/my-config.xml"); //1
Configuration xmlConfig = new XmlConfiguration(myUrl); //2
CacheManager myCacheManager = CacheManagerBuilder.newCacheManager(xmlConfig); //3
- 添加XML路徑。
- 實例化XMLConfiguration。
- 用靜態方法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,或者清空整個緩存, 用在執行了數據的增刪改時,這三種情況下,都應該讓緩存失效。
緩存執行流程:
- 先訪問的是設置緩存類的代理對象 (由於cglib 生成該類的子類對象作為代理對象)。
- 代理對象重寫了目標方法, 在該方法內, 通過緩存管理器(cache)找對應緩存名稱的緩存。
- Cache.get(key)去獲取value, 第一次訪問value為空, 執行的是原本的方法。
- 該類的方法返回結果作為value放入緩存中。
- 第二次訪問的時候,先訪問的是該類的代理對象。
- 代理對象重寫了目標方法,方法內部,通過緩存管理器(cacheManager)找到對應緩存。
- Cache.get(key)去獲取對應value, 返回value不為空,直接返回緩存中的value, 沒有執行原本的方法,而是執行代理對象中的目標方法。
緩存一般使用在讀多寫少的程序中。