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();

results matching ""

    No results matching ""