EhCache 是老牌Java开源缓存框架,早在2003年就已经出现了,发展到现在已经非常成熟稳定,在Java应用领域应用也非常广泛,而且和主流的Java框架比如Srping,Hibernate可以很好集成。相比于 Guava Cache,EnCache 支持的功能更丰富,包括堆外缓存、磁盘缓存,当然使用起来要更重一些。使用 Ehcache 的Maven 依赖如下:
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.3.1</version>
</dependency>
Ehcache 的支持两种缓存对象的初始化方式,一种是通过纯Java 代码的配置方式和 XML 文件的配置方式,代码中的每一项配置都有对应的XML配置项,使用Java 代码方式初始化如下:
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
//手动初始化
cacheManager.init();
ResourcePoolsBuilder resource = ResourcePoolsBuilder.heap(10);
CacheConfiguration<Long, UserInfo> config = CacheConfigurationBuilder
.newCacheConfigurationBuilder(Long.class, UserInfo.class, resource).build();
//创建缓存对象
Cache<Long, UserInfo> cache = cacheManager.createCache("userInfoCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(config));
//写入缓存
cache.put(10001L, new UserInfo());
//读取缓存
System.out.println(cache.get(10001L));
//再程序关闭前,需要手动释放资源
cacheManager.close();
在 Ehcache 中,创建缓存对象之前,需要手动初始化 CacheManager,初始化方式有两种,可以调用CacheManager 对象的 init() 方法。也可以在创建时将 CacheManagerBuilder.build(boolean init) 方法参数设置为 true。再本例中,设置堆内缓存容量为10条堆内缓存,缓存对象的别名叫“userInfoCache”。该缓存对象对应的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="userInfoCache">
<key-type>java.lang.Long</key-type>
<value-type>com.coderxing.demo.UserInfo</value-type>
<resources>
<heap unit="entries">10</heap>
</resources>
</cache>
</config>
加载 XML 的配置为:
URL configXmlFile = this.getClass().getResource("ehcache_config.xml");
Configuration xmlConfig = new XmlConfiguration(configXmlFile);
CacheManager cacheManager = CacheManagerBuilder.newCacheManager(xmlConfig);
cacheManager.init();
Cache<Long, UserInfo> cache = cacheManager.getCache("userInfoCache", Long.class, UserInfo.class);
cacheManager.close();
作为本地缓存框架, Encache支持多层缓存模式,常用的有三种数据存储模式:
堆内缓存(on-heap):
这种模式使用JVM 中的堆空间来保存缓存对象,是最常见的缓存模式,好处是可以直接存储Java对象数据结构,而不需要强制序列化,前面介绍的 Guava Cache 就只支持堆内缓存,缺点是会挤占JVM内存空间,会引发GC(垃圾回收)的频次和时间会变长。
通过代码设置配置对内缓存的方式如下:
// 按照条目数限制缓存容量,这里设置最多存储10条,如果缓存满了,则回收缓存
ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10, EntryUnit.ENTRIES);
// 也是按照条目数设置缓存容量的简写方式
ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10);
// 按照内存占用多少字节容量设置缓存空间的大小,例子中设置为最多占用10MB,存满则回收缓存
ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10, MemoryUnit.MB);
通过如下配置,可以更精确控制缓存对象:
- withSizeOfMaxObjectGraph() 方法设置缓存对象的最大遍历深度,建设不要设置过大。
- withSizeOfMaxObjectSize() 方法限制单个缓存对象的大小,超过这两个限制的对象则不被缓存。
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(10, MemoryUnit.KB)
.offheap(10, MemoryUnit.MB))
.withSizeOfMaxObjectGraph(3)
.withSizeOfMaxObjectSize(4, MemoryUnit.KB)
.build();
以上两个限制还可以通过 CacheManagerBuilder 进行全局配置,例如:
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withDefaultSizeOfMaxObjectSize(500, MemoryUnit.B)
.withDefaultSizeOfMaxObjectGraph(2000)
.build();
堆外缓存(Off-Heap)
相比于堆内缓存,堆外缓存使用JVM对以外的内存空间,也就意味着可以使用更大的内存空间,空间大小只受限于本机内地大小,而且不受GC管理(内存对象转移,释放等等。。),整体上可以减少GC带来的停顿影响,但缺点是,堆外内存中的对象必须要序列化,键和值必须实现Serializable接口,因此存取速度上会比堆内缓存慢。在Java中可以通过 -XX:MaxDirectMemorySize 参数设置堆外内存的上限。
// 堆外内存不能按照存储条目限制,只能按照内存大小进行限制,超过限制则回收缓存
ResourcePoolsBuilder.newResourcePoolsBuilder().offheap(200, MemoryUnit.MB);
磁盘缓存(Disk)
缓存数据存储在磁盘上,可以持久化存储数据可以不丢失,也意味着可以使用更大的存储空间,缓存在磁盘上的数据也需要支持序列化,速度会被比内存更慢,在使用时推荐使用更快的磁盘带来更大的吞吐率,比如使用闪存代替机械磁盘。在Ehcache中使用 PersistentCacheManager 来管理磁盘存储,还要指定一个磁盘存储路径,例如:
CacheManagerConfiguration<PersistentCacheManager> persistentManagerConfig = CacheManagerBuilder
.persistence(new File("/tmp", "ehcache-test"));
PersistentCacheManager persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(persistentManagerConfig).build(true);
//disk 第三个参数设置为 true 表示将数据持久化到磁盘上
ResourcePoolsBuilder resource = ResourcePoolsBuilder.newResourcePoolsBuilder().disk(100, MemoryUnit.MB, true);
CacheConfiguration<UserCacheKey, UserInfo> config = CacheConfigurationBuilder
.newCacheConfigurationBuilder(UserCacheKey.class, UserInfo.class, resource).build();
Cache<UserCacheKey, UserInfo> cache = persistentCacheManager.createCache("userInfoCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(config));
cache.put(new UserCacheKey(), new UserInfo());
persistentCacheManager.close();
执行上面的例子,我们会在 /tmp/ehcache-test/
中找到持久化的缓存文件,例如:
$ tree /tmp/ehcache-test/file/userInfoCache_6d32e4f84550d8f3077ac43b3767ff8686732002/
/tmp/ehcache-test/file/userInfoCache_6d32e4f84550d8f3077ac43b3767ff8686732002/
├── key-serializer
│ └── holder-0-CompactJavaSerializer-ObjectStreamClassIndex.bin
├── offheap-disk-store
│ ├── ehcache-disk-store.data
│ ├── ehcache-disk-store.index
│ └── ehcache-disk-store.meta
└── value-serializer
└── holder-0-CompactJavaSerializer-ObjectStreamClassIndex.bin
3 directories, 5 files
对于这三种缓存形式,Ehcache支持者三总模式的组合包括:
- heap + offheap
- heap + disk
- heap + offheap + disk
典型 heap + offheap+ disk 组合其结构图如下图所示:
上一级比下一级速度更快,另外,下一级比上一级存储空间更大,这也是ehcache的轻质要求,否则会报错。 ehcache 会将最热的数据保存在高一级的缓存,对应的代码如:
CacheManagerConfiguration<PersistentCacheManager> persistentManagerConfig = CacheManagerBuilder
.persistence(new File("/tmp", "ehcache-test"));
PersistentCacheManager persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(persistentManagerConfig).build();
// 手动初始化
persistentCacheManager.init();
// 空间大小 heap > offheap > disk,否则会报错
ResourcePoolsBuilder resource = ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(10, MemoryUnit.MB)
.offheap(100, MemoryUnit.MB)
//第三个参数设置为true,支持持久化
.disk(500, MemoryUnit.MB, true);
CacheConfiguration<Long, UserInfo> config = CacheConfigurationBuilder
.newCacheConfigurationBuilder(Long.class, UserInfo.class, resource).build();
Cache<Long, UserInfo> cache = persistentCacheManager.createCache("userInfoCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(config));
//写入缓存
cache.put(10001L, new UserInfo());
// 读取缓存
System.out.println(cache.get(10001L));
// 再程序关闭前,需要手动释放资源
persistentCacheManager.close();
在 ehcache 的多层缓存结构中,最底层的称之为 Authoritative Tier,其余的缓存层称为 “Caching Tier”。Authoritative Tier 按照字面意意思可以理解为“权威层”,以为该层数据是最全的,其余层的数据都是该层的数据子集,只是临时存储数据,所以可以列为“缓存层”。比如 heap + offheap 的组合,offheap 就是 Authoritative Tier,heap,就是 Caching Tier;对于 heap + offheap + disk 的组合,disk 层就是 Authoritative Tier,heap + offheap 就是 Caching Tier。
缓存写操作的时序图:
读操作的是时序图:
对于写操作:在写缓存的时候,Ehcache 直接将数据写到 Authoritative Tier(最底层),同时将 Caching Tier 中的旧数据失效掉。
对于读操作:Ehcache 会直接从 Caching Tier 中读取信息(Caching Tier 要比 Authoritative Tier 速度更快),如果读到,则直接返回。如果读不到,则从 Authoritative Tier 中再次读取,如果读到,则先将数据写入 Caching Tier,让后在返回,如果Authoritative Tier 读不到,则返回null。
缓存失效:
Ehcache 通过 Expirations 来这是缓存失效时间,示例代码如:
CacheConfiguration<Long, String> cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.heap(100))
.withExpiry(Expirations.timeToLiveExpiration(Duration.of(20, TimeUnit.SECONDS)))
.build();
Expirations 有三种方式:
- 永不失效:Expirations.noExpiration();
- TTL(time-to-live):自缓存项创建之后多久失效,例如:Expirations.timeToLiveExpiration(Duration.of(10,TimeUnit.MINUTES));
- TTI(time-to-idle):缓存项自上次方位之后空闲(没有被再次访问)多久后失效,例如:Expirations.timeToIdleExpiration(Duration.of(5, TimeUnit.MINUTES));
另外,Ehcache 中还可以通过自行实现Expiry
并发级别设置:
通过 withDispatcherConcurrency设置并发级别,
CacheConfigurationBuilder
.newCacheConfigurationBuilder(UserCacheKey.class,UserInfo.class, resource).withDispatcherConcurrency(16).build();