原创:实现ehcache动态创建cache,以及超期判断的具体逻辑

当前最常用的三个缓存组件:ehcache、redis、memcached

其中,ehcache与应用共同运行于JVM中,属于嵌入式组件,运行效率最高,因此常被用于实现一级缓存。

在更复杂的一些系统中,由于ehcache对集群/分布式的支持相对较弱,因此还会集成redis、memcached等,实现二级缓存。

ehcache的用法非常简单,只需要引入相关的Jar包,并创建一个配置文件,就可以在开发中使用了。

 

1、在Maven中添加ehcache的依赖:

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.4</version>
</dependency>

这个版本是发布到net.sf.ehcache的最后一个版本,也是2.x的最后一个版本。

org.ehcache上有更新的3.x版本,功能更强大,写法差异也挺大。

由于2.x的核心功能已经非常稳定,已经完全满足系统需求,也更熟悉,因此我还是选择了这个2.10.4的版本。

 

2、创建配置文件:ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"
         updateCheck="false" 
         dynamicConfig="false">

    <diskStore path="java.io.tmpdir/myApp"/>

    <!-- 
    默认缓存
    
    属性说明:
        maxElementsInMemory:内存中可保存的最大数量
        eternal:缓存中对象是否为永久的。如果是,超时设置将被忽略
        timeToIdleSeconds:对象最后一次访问之后的存活时间
        timeToLiveSeconds:对象创建后的存活时间
        memoryStoreEvictionPolicy:内存缓存的超期清理策略
        maxElementsOnDisk:硬盘中可保存的最大数量
        diskExpiryThreadIntervalSeconds:磁盘超期监控线程扫描时间间隔
        overflowToDisk:内存不足时,是否启用磁盘缓存
    -->
    <defaultCache 
        maxElementsInMemory="10000" 
        eternal="false"
        timeToIdleSeconds="1200" 
        maxElementsOnDisk="10000000"
        diskExpiryThreadIntervalSeconds="120" 
        memoryStoreEvictionPolicy="LRU"
        overflowToDisk="true">
    </defaultCache>
    
</ehcache>

把这个配置文件保存到 src/main/resource 目录下即可。

 

3、实现动态创建Cache

在ehcache中,有两个最基本的对象:Cache Element

其中,Cache相当于ehcache的分区,可以看成memcache中的Slab,上面配置文件中的 defaultCache 就是一个默认分区

每个Cache(分区)都类似一个Map<K, V>,可以通过Key来从Cache中返回缓存的Value

每个KV键值对,在ehcache中,被称为Element(元素)。

 

要将任何一个对象添加到ehcache中,都需要事先指定分区

但在配置文件中创建分区很麻烦,通常只创建一个默认分区(必须存在),然后通过一个方法来动态创建分区:

/**
 * 获取Cache,当Cache不存在时自动创建
 * 
 * @param cacheName
 * @return Cache
* @author netwild@qq.com */ public Cache getOrAddCache(String cacheName) { Cache cache = cacheManager.getCache(cacheName); if (cache == null) { synchronized (locker) { cache = cacheManager.getCache(cacheName); if (cache == null) { cacheManager.addCacheIfAbsent(cacheName); cache = cacheManager.getCache(cacheName); } } } return cache; }

这样的话,只需要像下面的用法,就可以很方便的把对象添加到缓存中:

String cacheName = "article";
String atricleId = "A00428";
Atricle article = AtricleService.findById(atricleId);
Element element = new Element(atricleId, article); getOrAddCache(cacheName).put(element);

动态创建的Cache并不会出现在ehcache.xml配置文件中。

值得注意的是,上面动态创建Cache的方法中,并没有为新的Cache指定任何参数,那这些参数的默认值是多少呢?

其实,当创建Cache时,如果未传入参数默认值,将自动拷贝 defaultCache 的参数设置

就是说,配置文件中 defaultCache 的超期时间等属性将直接被应用到所有动态创建的Cache。

 

4、ehcache关于元素超期的判断逻辑

在ehcache.xml配置文件中,有两个关于元素超期的参数:

timeToLiveSeconds:对象创建后的存活时间

timeToIdleSeconds:对象最后一次访问之后的存活时间

这两个参数我忽略了将近一年的时间,一直在配置文件中对他们都设置了同样的参数值,比如:1200(20分钟)

刚才反复实验多次,终于将这两个参数搞清楚,才明白以前的做法是错误的

首先,这两个参数都可以单独设置而省略另一个,也可以分别设置成不同的值,当然也可以设置成相同的值,就像我以前做的那样

下面分别对这几种进行说明

1)单独设置 timeToLiveSeconds

该对象的超期时间 = 初始创建时间 + timeToLiveSeconds

因为初始创建时间是固定的,因此不管这个对象在有效期内被命中了多少次,一旦满足超期条件,该对象将被移除。

2)单独设置 timeToIdleSeconds

该对象的超时时间 = 最近访问时间 + timeToIdleSeconds

注意与上面的区别:不再根据创建时间,而是根据最近访问时间来确定超期时间

所以这是一种动态的超期模式,即使这个参数设置为1(秒),只要保证每秒内都能get一次,那么对象也将永远不会超期。

3)分别设置 timeToLiveSeconds 及 timeToIdleSeconds

那么将分别计算以上两种模式的超期时间,会得出两个结果,再从两个结果里找到最小的一个做为超期时间,相当于“严苛模式

但事实上,创建时间肯定会小于最近访问时间,那如果两者都设置同样的参数值,相当于 timeToIdleSeconds 永远也不会起到作用。

如果设置不同的参数值,根据具体的业务需求,可能会出现一些意料之中或者意料之外的情况。

 

综上所述,我的建议是,单独设置 timeToIdleSeconds 更恰当一些,对于在有效期内被频繁命中的缓存对象,可以自动“续期”。

 

5、最常用的操作之一:判断缓存中是否存在对象

 

在应用缓存的开发过程中,这是最常用的操作,目的是想要知道:目标对象是否已经被缓存过

通常下面的逻辑是:如果已被缓存过,那么直接拿出来使用;否则自力更生,完事之后再添加到缓存,下次就省事了

很多人是这样判断的:

return getOrAddCache(cacheName).get(key) != null;

但这种方式存在个问题:当缓存对象实际上存在,但值就是Null,这时就相当于忽略了缓存

所以我开始时是这样判断的:

return getOrAddCache(cacheName).isKeyInCache(key);

后来发现这种方式不会进行超期验证,就是说即使对象已经超期,只要当初被创建过,也会返回true

调整之后:

Cache cache = getOrAddCache(cacheName);
if(cache.isKeyInCache(key) && cache.getQuiet(key) != null){
    return true;
}
return false;

这样就准确了!

 

posted @ 2018-03-03 17:57  网无忌  阅读(3576)  评论(1编辑  收藏  举报