前言:EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。Spring 提供了对缓存功能的抽象:即允许绑定不同的缓存解决方案(如Ehcache),但本身不直接提供缓存功能的实现。它支持注解方式使用缓存,非常方便。
问题:ehCache使用起来是非常便捷方便的,并且ehcache支持三级缓存,分别是:堆内内存(heap),堆外内存(offHeap),硬盘缓存(disk),正常我们需要根据项目的不同,手动配置这些变量的最大上限,比如堆内缓存需要配置最大个数,堆外内存需要配置最大内存使用,硬盘同样需要配置最大存储使用,但是官方并没有提供一个合适的监控当前使用余量的接口,那么我就来尝试手动实现下吧。
1,配置相关
1.1 ehcache3.x的配置相当的简单
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.1</version>
</dependency>
1.2 cache manager配置
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("preConfigured",
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class,
ResourcePoolsBuilder.heap(100))
.build())
.build(true);
Cache<String, String> myCache = cacheManager.createCache("myCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class,
ResourcePoolsBuilder.heap(100).offheap(300, MemoryUnit.MB)).build());
这里我自定义了一个myCache的缓存容器,其中ResourcePoolsBuilder.heap 就是堆内内存最大个数限制,我设置了100个,offheap就是最大堆外内存限制,我设置了300MB,还有disk硬盘最大存储限制,我这里没有使用就没有写了。
2.debug过程
2.1代码实现
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("preConfigured",
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class,
ResourcePoolsBuilder.heap(100))
.build())
.build(true);
Cache<String, String> myCache = cacheManager.createCache("myCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class,
ResourcePoolsBuilder.heap(100).offheap(300, MemoryUnit.MB)).build());
for (int i = 0; i < 1000; i++) {
myCache.put("iiiiii"+i,"bbbbbbbb"+i);
}
cacheManager.close();
这里我设置了循环插入1000个缓存,我来debug看下。
这里我对myCache对象进行debug,发现里面有个store的属性,其实就是存储不同存储类型容器的地方。
里面有个四个容器对象,其中我只要关注cachingTierRef和authoritativeTire这两个就可以,经过我的研究,这两个容器分别是堆内内存和堆外内存。
点开后发现,确实如此,这里维护了一个map属性,里面有个realMap,就是堆内内存真正存储的缓存容器(这里我塞了6个缓存进去,为啥这里一个都没有,我始终想不明白,最后找到答案,后面解释),而capacity属性,就是我前面设置的堆内内存最大缓存个数了,刚好是100个。
打开authoritativeTire发现,内部结构和堆内内存差不多,这里有两个属性,map和sizeInBytes,map容器里刚好是我刚刚塞进来的6个缓存,而sizeInBytes则是我设置的堆外内存最大内存空间,经过转换,发现值是对的,这个时候,我就已经得到了我想要的两个值:
堆外内存实际使用个数:myCache.store.authoritativeTier.map.size();
堆内内存实际使用个数:myCache.store.cachingTierRef.value.map.realMap.size();
现在还差我想要的最后一个值也是最重要的一个值,堆外内存实时使用内存容量。
忽略我查找的过程,直接看结果,我发现在堆外内存的map上,有dataOccupiedMemory这个一个方法,返回的刚好是实时map使用容量,通过debug我发现,实时返回的数据与我插入的数据变化一致。到这里,我就已经找到我监控ehcache最重要的三个值了。
3. 监控实现
public static Double dataOccupiedMemory(Cache<String, String> myCache) {
Ehcache ehcache = (Ehcache) myCache;
TieredStore tieredStore = null;
try {
for (Class<?> clazz = ehcache.getClass(); clazz != Object.class; clazz = ehcache.getClass().getSuperclass()) {
if (clazz.getName().equals("org.ehcache.core.EhcacheBase")) {
Field storeField = clazz.getDeclaredField("store");
storeField.setAccessible(true);
tieredStore = (TieredStore) storeField.get(ehcache);
break;
}
}
if (tieredStore == null) {
return new Double(0.00);
}
Field authoritativeTier = tieredStore.getClass().getDeclaredField("authoritativeTier");
authoritativeTier.setAccessible(true);
OffHeapStore offHeapStore = (OffHeapStore) authoritativeTier.get(tieredStore);
Class<? extends OffHeapStore> offHeapStoreClass = offHeapStore.getClass();
Field mapField = offHeapStoreClass.getDeclaredField("map");
mapField.setAccessible(true);
EhcacheConcurrentOffHeapClockCache map = (EhcacheConcurrentOffHeapClockCache)mapField.get(offHeapStore);
long l = map.dataOccupiedMemory();
return new BigDecimal(new Double(map.dataOccupiedMemory()) /1024/1024)
.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
} catch (Exception e) {
}
return new Double(0.00);
}
废话不多说,直接上代码,层级太深,我利用反射最后调用到了map的dataOccupiedMemory方法,获取到了我想要的最后一个值,堆外内存的实际使用情况。
4. 结言
太难了,中途差点放弃了,最后还是完成了,关于为啥堆内内存优先级最高,插入数据时却没有优先进入对内内存的容器中,最后我查阅相关资料得知,ehcache还维护着每个缓存的热度,当热度到达某个阈值的时候,就缓存就会从优先级低的容器进入优先级高的容器中,这也是合理的。