参考资料:

http://www.ehcache.org/documentation/3.2/getting-started.html

http://www.ehcache.org/documentation/3.2/eviction-advisor.html

示例代码:

https://github.com/gordonklg/study,cache module

A. HelloWorld

gordon.study.cache.ehcache3.basic.HelloWorld.java

public class HelloWorld {

    public static void main(String[] args) {
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
cacheManager.init();
Cache<Long, String> myCache = cacheManager.createCache("myCache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)));
myCache.put(1L, "Hello World!");
String value = myCache.get(1L);
System.out.println(value);
cacheManager.close();
}
}

代码第4行通过 Builder 模式创建了一个不可变的 CacheManager,用于管理所有的 Cache 及相关 Service。接着调用 init() 方法初始化 CacheManager。

代码第6行创建了一个 Cache,名字为 myCache。通过 CacheConfigurationBuilder 构建出的 CacheConfiguration 限制 myCache 只能接受 key 类型为 Long,value 类型为 String 的数据,同时 ResourcePoolsBuilder 构建出的 ResourcePools 决定了 myCache 可以使用 JVM 堆内存缓存最多10条数据。

代码第8行使用 myCache 缓存了一条数据,第9行从 myCache 中读取缓存数据。

代码第10行关闭 CacheManager,这会顺带着关闭所有的 Cache。

B. Cache 配置项

Cache 的特性通过 CacheConfigurationBuilder 创建出的 CacheConfiguration 决定。通过 CacheConfiguration 接口定义,可以看出 Cache 支持以下特性:

  • key type,设定允许的 key 类型,默认为 Object, 类型不匹配会抛出 ClassCastException
  • value type,设定允许的 value 类型,默认为 Object, 类型不匹配会抛出 ClassCastException
  • resource pools,设定缓存区的空间大小
  • expiry,设定过期策略
  • eviction advisor,调整回收策略

resource pools 设定缓存区的空间大小,Ehcache 总共有4种缓存区,分别为:

  • heap,堆内存,最常用,目前团队只要掌握这种就可以了
  • offheap,堆外内存,优势是不用GC
  • disk,磁盘空间,可以持久化
  • cluster,Ehcahce 已经支持集群了,具体内容不详

对于堆内存,resource pools 指定了 Cache 实例可以存多少条目以及最大占用多大内存空间。

过期策略有三种:

  • 不过期,条目一直存在于缓存中,除非显式从缓存中 remove,或者因回收策略被移除
  • TTL,time to live,指定条目的存活时间
  • TTI,time to idle,条目在指定时间段内未被使用,则过期

默认回收策略比较复杂,大致可视为 LRU 算法(最长时间未被访问的条目优先被回收)。可以通过 EvictionAdvisor 接口调整回收策略

示例代码如下:

gordon.study.cache.ehcache3.basic.CacheFeatures.java

public class CacheFeatures {

    @SuppressWarnings({ "rawtypes", "unchecked" })
public static void main(String[] args) throws Exception {
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true);
CacheConfiguration config = CacheConfigurationBuilder
.newCacheConfigurationBuilder(Integer.class, String.class, ResourcePoolsBuilder.heap(5))
.withEvictionAdvisor(new TopThreeAreImportantEvictionAdvisor())
.withExpiry(Expirations.timeToIdleExpiration(Duration.of(3, TimeUnit.SECONDS))).build();
Cache myCache = cacheManager.createCache("myCache", config);
try { // key type 错误
myCache.put("Invalid key type", "sad");
} catch (Exception e) {
System.out.println("Invalid key type");
}
try { // value type 错误
myCache.put(1L, 9527L);
} catch (Exception e) {
System.out.println("Invalid value type");
}
for (int i = 1; i <= 10; i++) { // 超出数量上限,回收策略开始生效:key为1、2、3的条目不被回收
myCache.put(i, "No. " + i);
}
printAllCacheEntries(myCache); System.out.println("-------------------------------");
for (int i = 0; i < 3; i++) { // 等待3秒,3秒内一直没被使用的条目全部过期移除。只有key为1的条目还在。
myCache.get(1);
Thread.sleep(1050);
}
printAllCacheEntries(myCache);
} private static void printAllCacheEntries(Cache<Integer, String> cache) {
cache.forEach(new Consumer<Cache.Entry<Integer, String>>() { @Override
public void accept(Cache.Entry<Integer, String> entry) {
System.out.println(entry.getKey());
}
});
} private static class TopThreeAreImportantEvictionAdvisor implements EvictionAdvisor<Integer, String> { @Override
public boolean adviseAgainstEviction(Integer key, String value) {
return (key.intValue() < 4);
}
}
}

代码第8行将回收策略设定为自定义的 TopThreeAreImportantEvictionAdvisor 回收策略,即 key 值小于4的条目尽量不回收。第24行打印的所有 Cache 条目证明了key为1、2、3的条目没有被回收。EvictionAdvisor 实现类的 adviseAgainstEviction 回调方法返回 true 表示不建议回收该条目,返回 false 表示可以回收。

代码第9行将过期策略设定为 TTI,3秒未被访问的条目会过期。第31行的打印结果证明了只有key为1的条目由于一直被访问还存在于 Cache 中。

C. 回收算法略读

Ehcache 默认回收算法是一种近似 LRU 算法,Ehcache 并没有将所有的条目按照最后访问时间放在一个有序的Map中,或者利用额外的数据结构来完成排序,而是通过遍历的方式在一个小范围内寻找 LRU 条目。回收的触发时机是在新的条目需要放置到 Cache 中时。

Cache 类的 Store store 属性用于 Cache 的后端存储实现。在本例中,store 类型为 OnHeapStore。对 Cache 类的 put 操作实际上就是对 OnHeapStore 的 put 操作。

OnHeapStore 的 put 操作会先创建条目,放入内部的 Map 中(本质上是 ConcurrentHashMap),然后判断当前条目数量是否超过允许的条目上限。如果超过上限,则先按照指定的 EvictionAdvisor 寻找一条可回收条目,如果找不到,则无视 EvictionAdvisor 再次寻找一条可回收条目(因为可能 Cache 中所有条目都置为不建议回收)。这段逻辑由 OnHeapStore 的 evict 方法实现(Line 1605)

    @SuppressWarnings("unchecked")
Map.Entry<K, OnHeapValueHolder<V>> candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, EVICTION_ADVISOR); if (candidate == null) {
// 2nd attempt without any advisor
candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, noAdvice());
}

寻找可回收条目的逻辑略复杂。宏观上说,是遍历 Cache 中的条目,分析最多8条可以回收的条目,选中 lastAccessTime 值最小的那个条目。时间对比通过 OnHeapStore 类中预先定义好的 Comparator EVICTION_PRIORITIZER 完成。显然,这并不是 LRU,只是小范围中的 LRU

  private static final Comparator<ValueHolder<?>> EVICTION_PRIORITIZER = new Comparator<ValueHolder<?>>() {
@Override
public int compare(ValueHolder<?> t, ValueHolder<?> u) {
if (t instanceof Fault) {
return -1;
} else if (u instanceof Fault) {
return 1;
} else {
return Long.signum(u.lastAccessTime(TimeUnit.NANOSECONDS) - t.lastAccessTime(TimeUnit.NANOSECONDS));
}
}
};

由于算法的特性,所以遍历 Cache 条目需要从一个随机点开始以优化算法的整体准确性。getEvictionCandidate 方法从一个随机起始点开始遍历 ConcurrentHashMap,分析可回收条目,如果分析完8个条目,则返回最长时间未被访问的条目;如果从随机起始点到最后一个条目之间不满8个可回收条目,则调用 getEvictionCandidateWrap 方法从0开始遍历 ConcurrentHashMap,直到分析完8个条目(包含getEvictionCandidate 方法中分析的条目)或到达上面的随机开始点为止(相当于遍历了全部的 Cache 条目),最终返回最长时间未被访问的条目。

按照这个逻辑,如果 Cache 空间已满且全是不建议回收条目,这时如果插入可回收条目,该条目立即会被回收;如果插入不建议回收条目,则随机回收一条。见下例:

gordon.study.cache.ehcache3.basic.CacheFeatures_Eviction.java

public class CacheFeatures_Eviction {

    private static final int CACHE_SIZE = 50;

    public static void main(String[] args) throws Exception {
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true);
CacheConfiguration<Integer, String> config = CacheConfigurationBuilder
.newCacheConfigurationBuilder(Integer.class, String.class, ResourcePoolsBuilder.heap(50))
.withEvictionAdvisor(new MyEvictionAdvisor()).build();
Cache<Integer, String> myCache = cacheManager.createCache("myCache", config);
for (int i = 1; i <= CACHE_SIZE; i++) {
myCache.put(i, "No. " + i);
Thread.sleep(10);
}
myCache.put(CACHE_SIZE + 1, "No. " + (CACHE_SIZE + 1)); BitSet bitSet = new BitSet(CACHE_SIZE + 1);
for (Cache.Entry<Integer, String> entry : myCache) {
bitSet.set(entry.getKey() - 1);
}
System.out.println("Eviction number: " + (bitSet.nextClearBit(0) + 1));
} private static class MyEvictionAdvisor implements EvictionAdvisor<Integer, String> { @Override
public boolean adviseAgainstEviction(Integer key, String value) {
return (key.intValue() <= CACHE_SIZE); // 最后一条是可回收条目,则必定回收这一条
// return (key.intValue() <= (CACHE_SIZE + 1)); // 最后一条是不建议回收条目,则随机回收一条
}
}
}

缓存技术内部交流_01_Ehcache3简介的更多相关文章

  1. 缓存技术内部交流_05_Cache Through

    参考资料: http://www.ehcache.org/documentation/3.2/caching-patterns.html http://www.ehcache.org/document ...

  2. 缓存技术内部交流_04_Cache Aside续篇

    额外参考资料: http://www.ehcache.org/documentation/3.2/expiry.html F. Cache Aside 模式的问题:缓存过期 有时我们会在上线前给缓存系 ...

  3. 缓存技术内部交流_03_Cache Aside

    参考资料: http://www.ehcache.org/documentation/3.2/caching-patterns.html http://www.ehcache.org/document ...

  4. 缓存技术内部交流_02_Ehcache3 XML 配置

    参考资料: http://www.ehcache.org/documentation/3.2/getting-started.html#configuring-with-xml http://www. ...

  5. 分布式缓存技术redis学习系列(一)——redis简介以及linux上的安装

    redis简介 redis是NoSQL(No Only SQL,非关系型数据库)的一种,NoSQL是以Key-Value的形式存储数据.当前主流的分布式缓存技术有redis,memcached,ssd ...

  6. 分布式缓存技术redis学习(一)——redis简介以及linux上的安装

    redis简介 redis是NoSQL(No Only SQL,非关系型数据库)的一种,NoSQL是以Key-Value的形式存储数据.当前主流的分布式缓存技术有redis,memcached,ssd ...

  7. 分布式缓存技术redis系列(一)——redis简介以及linux上的安装

    redis简介 redis是NoSQL(No Only SQL,非关系型数据库)的一种,NoSQL是以Key-Value的形式存储数据.当前主流的分布式缓存技术有redis,memcached,ssd ...

  8. .Net环境下的缓存技术介绍 (转)

    .Net环境下的缓存技术介绍 (转) 摘要:介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1         概念 ...

  9. [.net 面向对象程序设计进阶] (14) 缓存(Cache) (一) 认识缓存技术

    [.net 面向对象程序设计进阶] (14) 缓存(Cache)(一) 认识缓存技术 本节导读: 缓存(Cache)是一种用空间换时间的技术,在.NET程序设计中合理利用,可以极大的提高程序的运行效率 ...

随机推荐

  1. JavaWeb中servlet读取配置文件的方式

    我们在JavaWeb中常常要涉及到一些文件的操作,比如读取配置文件,下载图片等等操作.那我们能不能采用我们以前在Java工程中读取文件的方式呢?废话不多说我们来看看下我们以前在Java工程中读取文件是 ...

  2. YOLO v1论文笔记

    You Only Look Once:Unified, Real-Time Object Detection   论文链接:https://arxiv.org/abs/1506.02640 Homep ...

  3. 高德js API moveAlong 函数的一个错误解决

    使用覆盖物之一:点标记,让点标记沿着固定的路线移动. API 提供了现成的函数 moveAlong() 开始以为 实现移动很简单:分两部 1.准备好经纬度数组 2.调用moveAlong()函数.按照 ...

  4. 微信商城 Common Log Format Apache CustomLog

    w 0- /Apr/::: +] "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, ...

  5. 定时备份DB和WEB文件

    sql.sh #!/bin/bash ##备份数据库: 每4小时 date2=`date "+%Y-%m-%d---%H.%M.%S"` /alidata/server/mysql ...

  6. Centos 软链接命令(十)

    链接命令:ln  (link) ln -s [源文件] [目标文件] 功能描述:生成链接文件 选项: -s 创建软链接 硬链接特征: 1,拥有相同的i节点和存储block块,可以看作是同一个文件: 2 ...

  7. PyMongo的使用(转)

    原文:http://www.oschina.net/code/snippet_1382328_37407 #!/usr/bin/env python #coding:utf-8 # Author: - ...

  8. python数据类型一(重点是字符串的各种操作)

    一.python基本数据类型 1,int,整数,主要用来进行数学运算 2,bool,布尔类型,判断真假,True,False 3,str,字符串,可以保存少量数据并进行相应的操作(未来使用频率最高的一 ...

  9. 004-安装CentOS7后需要的操作

    1 安装EPEL源 EPEL即Extra Packages for Enterprise Linux,是基于Fedora的一个项目,为红帽系的操作系统提供额外的软件包,适用于RHEL.CentOS和S ...

  10. shell sed 命令

    1:行首空格  sed 's/^[ \t]*//g'  2:行末空格 sed 's/[ \t]*$//g' 3,删除行首的空格或TAB,并删除<tr>.cat poem2id.txt | ...