参考资料:

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. spring + quartz 定时

    springConfig配置文件: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns=& ...

  2. 常用的mysql语句

    为了方便学习mysql,把接触到的sql收集一下,忘记的时候可以查询一下. 连接mysql数据库: mysql -u 用户名 -p 输入密码. 创建数据库: create database 数据库名; ...

  3. Go语言的一些资料汇总

    1. 8分钟了解你为什么应该学习Go语言 https://www.bilibili.com/video/av45561733/ 2.手把手教你从零开始搭建Go语言开发环境 https://www.bi ...

  4. iPhone设备分辨率一览

    地址:https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/launch-screen/

  5. submit按钮修改宽高的坑

    近些天对h5非常感兴趣,边工作边学习,虽然比较累,但过得很踏实.每天都要学习一点东西,这样才能对得起自己.好了,废话不多说,进入今天的主题. 今天遇到了一个非常有趣的东西,就是在修改submit按钮的 ...

  6. Redis与Memcached的比较(转)

    原文:http://blog.nosqlfan.com/html/3729.html 这两年Redis火得可以,Redis也常常被当作Memcached的挑战者被提到桌面上来.关于Redis与Memc ...

  7. 我的Android进阶之旅------>关于android:layout_weight属性的详细解析

    关于androidlayout_weight属性的详细解析 效果一 效果二 图3的布局代码 图4的布局代码 效果三 图7代码 图8代码 效果四 效果五 版权声明:本文为[欧阳鹏]原创文章,欢迎转载,转 ...

  8. Android之网络----使用HttpClient发送HTTP请求(通过get方法获取数据)

    [正文] 一.HTTP协议初探: HTTP(Hypertext Transfer Protocol)中文 "超文本传输协议",是一种为分布式,合作式,多媒体信息系统服务,面向应用层 ...

  9. Python3+Selenium3自动化测试-(三)

    selenium键盘事件 #coding=utf-8 from selenium import webdriver import time from selenium.webdriver.common ...

  10. mui请求数据

    var rh = new Object(); rh.ReqId = "ls123"; rh.Salt = "sssseee"; var rb = new Obj ...