Ehcache缓存从2.10升到3.5.0版本的简单用法和踩坑经验

White_dove

简介:Ehcache是一个开源的,基于标准的缓存,可提升性能,卸载数据库,简化可扩展性。它是最广泛使用的基于Java的缓存。

1、echache2.xx和3.xx版本的pom引用

1.png
3.X的版本升级后不兼容2.X

2、echache3.X版本的简单用法,和2.X版本的差异

2.1 创建cache要点:(以3.X版本为例)
(1)需要一个CacheManager来管理cache。
(2)需要一个CacheConfiguration来制定创建cache的条件。
(3)可以创建CacheManager的同时创建一个cache,并确定缓存的key和value类型、堆内(heap)大小,根据需要设置堆外(offheap) 和持久化磁盘等等。

简单例子:(设置cache别名“preConfigured”,根据别名获取cache,进行存取操作)
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("preConfigured", CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)))
.build();

cacheManager.init();//初始化缓存。或者在上面设置.build(true)来初始化。使用cacheManager时必须要初始化。

//获取cache
Cache<Long, String> preConfigured = cacheManager.getCache("preConfigured", Long.class, String.class);
//与2.X版本的差异点,2.X存一个Element元素(包含键值对),3.X直接存一个键值对
preConfigured.put(1L, "one");
//根据key获取
String value = preConfigured.get(1L);
//删除cache
cacheManager.removeCache("preConfigured");
//若需要释放CacheManager提供给它管理的cache实例的所有瞬时资源(内存、线程等),调用CacheManager.close()来关闭所有的cache实例
cacheManager.close();

持久化硬盘的例子:
// 初始化配置缓存管理器,设置堆内存大小、磁盘持久化路径、磁盘存储缓存大小

PersistentCacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(CacheManagerBuilder.persistence(storePath + File.separator + tempFilePath + File.separator))
//持久化硬盘路径
.withCache("preConfigured", CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, CacheObj.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(40, MemoryUnit.MB) //堆内,单位可选KB、MB、GB,也可设缓存的单位个数
.offheap(50, MemoryUnit.MB) //堆外
.disk(10, MemoryUnit.GB, true))) //硬盘,第三个boolean参数设置是否持久化
.build(true);//创建并初始化

(4)也可以创建完CacheManager之后,再创建一个新的cache实例,制定配置。

private Cache<Long, String> createCache(String cacheName, CacheManager cacheManager) {
//设置cache的配置
CacheConfiguration<Long, String> cacheConfiguration = newCacheConfigurationBuilder(Long.class, String.class, heap(10))
.withSizeOfMaxObjectGraph(1000)//可选配置,遍历一个对象图的时候的最多对象数
.withSizeOfMaxObjectSize(1000, MemoryUnit.B)//withSizeOfMaxObjectSize,单个对象的最大size .withExpiry(Expirations.timeToLiveExpiration(Duration.of(20, TimeUnit.HOURS)))//withExpiry过期策略,timeToLiveExpiration是cache生存时间,即创建cache后可以存在多久;timeToIdleExpiration cache闲置时间,即最近一次使用cache之后最多可以闲置多久;noExpiration 永不过期
.build();

//创建一个cache实例
Cache<Long, String> myCache = cacheManager.createCache(cacheName,cacheConfiguration);

return myCache;
}

官方参考文档:http://www.ehcache.org/docume...

2.2 cache的存储方式
Ehcache提供4种存储策略:heap、offheap、disk、clustered
存储速度依次降低,主要使用前3种做本地缓存。

(1)heap就是指java堆内存,这个会被GC,所以最好数量不要太大。太多的cache会造成堆内存使用减少,参考JVM堆内存设置。heap不需要序列化和反序列化。(坑点之一)

(2)offheap是堆外内存,不会受GC影响,但须以value形式存储cache,需要序列化和反序列化,性能比heap慢。启用offheap必须要确定是否开启了-XX:MaxDirectMemorySize=size[g|G|m|M|k|K],如果没设置则默认不开启,就无法使用offheap

(3)disk是磁盘存储,需要指定存储路径,速度也更慢。

(4)clustered是集群层缓存方式,表示一个客户端连接到存储缓存的服务器集群,也是JVM之间共享缓存的一种方式。一般不用,感兴趣的可以看官方文档了解。

可以搭配使用的存储层方案:
heap + offheap
heap + offheap + disk
heap + disk
heap + offheap + clustered
heap + clustered

官方参考文档:http://www.ehcache.org/docume...

2.3 Ehcache3.X和2.X版本使用差异
3.X版本不兼容2.X的用法,2.X版本的用法参考:https://www.cnblogs.com/qlqwj...

使用过程的差异点:
(1)缓存管理器CacheManager的创建和配置方式不同
(2)缓存配置CacheConfiguration的各种属性用法不同
(3)存取元素方式不同,2.X版本最大特点是使用了Element作为一个缓存元素对象,3.X版本不支持这种存取方式。
(4)其它待补充...

2.4 逐出策略

在2.x版本,我们可以选择LRU以及FIFO、LFU,CacheConfiguration配置memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)。在3.x中提供了EvictionAdvisor,通过继承此类可以实现采取哪种方式逐出。3.X版本官方不建议逐出,默认满了就不添加了,这里没做研究,可参考官方文档。

3、升级过程的坑点

3.1因为存取元素的方式不同,所以重构代码时,需要对存放的value数据类型做特殊处理。

value支持存放对象类型,由于我们使用三层存储方式heap+offheap+disk,所以value需要序列化和反序列化,自定义对象类必须实现序列化。

下面对比升级前后的存取写法:
2.X版本:核心在于Element元素
2.png

3.X版本:value存放的CacheObj对象类实现了序列化
3.png

3.2 踩坑注意:
你以为value存的对象类CacheObj实现序列化就OK了吗,一开始我也这么认为,所以不断调试了很多次但是结果都扑街,一度怀疑我的代码人生。
这里CacheObj类确实要序列化,并且里面存放的子对象也要实现序列化。下图画红圈处。
6.png
单元测试接口只是构造了简单的String数据,测试存取没问题后就一直去测业务功能了。所以提醒大家新写接口时不仅要单元测试,还要构造和实际业务参数一样的参数类型。

这里Object我们传进来的是一个Port对象类,Port类也实现了序列化,这就奇怪了不可能还有问题啊?! 后面不断debug到组件的内部源代码,put进去时抛了一个StoreAccessException,搜索了一下说是存放的类型问题或没序列化时会报这个错。。。最后调试发现java.io.NotSerializableException异常,检查Port类发现里面竟然重写了readObject和writeObject方法并且直接抛出不能序列化异常。(坑die)

3.3 业务功能测试踩坑
Map<Object, Object> entitys = getResultMap(neUtil);//获取缓存方法
每次获取缓存并赋给entitys,这里entitys对象和getResultMap(neUtil) 应该指向同一块内存空间。
后面每次获取数据后填充entitys对象。
所以当entitys对象值修改时,缓存内容相应的也修改才对。
原来的实现逻辑是这样的。

但是升级ehcache组件后,打日志调试发现每次entitys对象填充后,getResultMap(neUtil)的值并没有一起改变。
所以我在后面加了一行,每次填充entitys对象后立刻put到缓存里。测试结果OK。
这里猜想可能是缓存和内存的存取时间差影响,打断点时也是可以取到,或者ehcache升级后不再使用Element元素,直接存取键值对导致对象地址变了?可以留言一起探讨哦。

到这里,ehcache组件升级算是告一段落。还有很多特性配置和性能方面没有介绍。感兴趣可参考官方文档。
我们秋名山再见!

ehcache参考链接:
3.5版本官方文档 http://www.ehcache.org/docume...
https://blog.rmiao.top/Ehcach...
https://www.jianshu.com/p/5a0...
2.5版本用法:https://www.cnblogs.com/qlqwj...

阅读 4k

这代码开始一个人,你认真写成了我们

0 声望
0 粉丝
0 条评论
你知道吗?

这代码开始一个人,你认真写成了我们

0 声望
0 粉丝
宣传栏