当前最常用的三个缓存组件: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;
这样就准确了!