基于ehcache实现缓存的常用方法

目前最常用的三种缓存方案:

  1. ehcache
  2. redis
  3. memcached

其中的redis和memcached都是以TCP/IP的形式实现分布式缓存,而ehcache需要集成到项目中,与应用共享JVM环境,属于嵌入式组件。因为不需要额外的网络开销,因此在这三者中的运行效率最高,常被用于实现一级缓存。

添加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 这个版本就可以。

配置文件: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

实现动态创建Cache

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

  1. Cache
  2. Element

其中,Cacheehcache 的分区,可以看成 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

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

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

  1. timeToLiveSeconds:对象创建后的存活时间
  2. timeToIdleSeconds:对象最后一次访问之后的存活时间

这两个参数我忽略了将近一年的时间,一直在配置文件中对他们都设置了同样的参数值,比如:1200(20分钟)
刚才反复实验多次,终于将这两个参数搞清楚,才明白以前的做法是错误的。
首先,这两个参数都可以单独设置而省略另一个,也可以分别设置成不同的值,当然也可以设置成相同的值,就像我以前做的那样。

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

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

  2. 单独设置 timeToIdleSeconds
    该对象的超时时间 = 最近访问时间 + timeToIdleSeconds
    注意与上面的区别:不再根据创建时间,而是根据最近访问时间来确定超期时间。
    所以这是一种动态的超期模式,即使这个参数设置为1(秒),只要保证每秒内都能get一次,那么对象也将永远不会超期。

  3. 分别设置 timeToLiveSecondstimeToIdleSeconds
    那么将分别计算以上两种模式的超期时间,会得出两个结果,再从两个结果里找到最小的一个做为超期时间,相当于“严苛模式
    但事实上,创建时间肯定会小于最近访问时间,那如果两者都设置同样的参数值,相当于 timeToIdleSeconds 永远也不会起到作用。
    如果设置不同的参数值,根据具体的业务需求,可能会出现一些意料之中或者意料之外的情况。

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

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

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

通常下面的逻辑是:

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

很多人是这样判断的:

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;

这样就准确了!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容

  • 理论总结 它要解决什么样的问题? 数据的访问、存取、计算太慢、太不稳定、太消耗资源,同时,这样的操作存在重复性。因...
    jiangmo阅读 2,844评论 0 11
  • 原文连接:https://my.oschina.net/coolfire368/blog/123377 ehcac...
    晴天哥_王志阅读 1,345评论 0 1
  • Ehcache是现在最流行的纯Java开源缓存框架,配置简单、结构清晰、功能强大,最初知道它,是从Hibernat...
    安易学车阅读 2,030评论 0 11
  • 前言 Ehcache 是一个成熟的缓存框架,你可以直接使用它来管理你的缓存。Spring 提供了对缓存功能的抽象:...
    静默虚空阅读 2,466评论 3 20
  • Spring整合Ehcache管理缓存 前言 Ehcache 是一个成熟的缓存框架,你可以直接使用它来管理你的缓存...
    人在码途阅读 1,269评论 0 1