Java本地缓存框架系列-Caffeine-1. 简介与使用
Caffeine 是一个基于Java 8的高性能本地缓存框架,其结构和 Guava Cache 基本一样,api也一样,基本上很容易就能替换。 Caffeine 实际上就是在 Guava Cache 的基础上,利用了一些 Java 8 的新特性,提高了某些场景下的性能效率。
这一章节我们会从 Caffeine 的使用引入,并提出一些问题,之后分析其源代码解决这些问题来让我们更好的去了解 Caffeine 的原理,更好的使用与优化,并且会对于我们之后的编码有所裨益。
我们来看一下 Caffeine 的基本使用,首先是创建:
限制缓存大小
Caffeine 有两种方式限制缓存大小。两种配置互斥,不能同时配置
1. 创建一个限制容量 Cache
Cache<String, Object> cache = Caffeine.newBuilder()//设置缓存的 Entries 个数最多不超过1000个.maximumSize(1000).build();
需要注意的是,实际实现上为了性能考虑,这个限制并不会很死板:
- 在缓存元素个数快要达到最大限制的时候,过期策略就开始执行了,所以在达到最大容量前也许某些不太可能再次访问的 Entry 就被过期掉了。
- 有时候因为过期 Entry 任务还没执行完,更多的 Entry 被放入缓存,导致缓存的 Entry 个数短暂超过了这个限制
配置了 maximumSize 就不能配置下面的 maximumWeight 和 weigher
2. 创建一个自定义权重限制容量的 Cache
Cache<String, List<Object>> stringListCache = Caffeine.newBuilder()//最大weight值,当所有entry的weight和快达到这个限制的时候会发生缓存过期,剔除一些缓存.maximumWeight(1000)//每个 Entry 的 weight 值.weigher(new Weigher<String, List<Object>>() {@Overridepublic @NonNegative int weigh(@NonNull String key, @NonNull List<Object> value) {return value.size();}}).build();
当你的缓存的 Key 或者 Value 比较大的时候,想灵活地控制缓存大小,可以使用这种方式。上面我们的 key 是一个 list,以 list 的大小作为 Entry 的大小。
当把 Weigher 实现为只返回1,maximumWeight 其实和 maximumSize 是等效的。
同样的,为了性能考虑,这个限制也不会很死板。
在这里,我们提出第一个问题:Entry是怎么保存,怎么过期的呢?
3. 指定初始大小
Cache<String, Object> cache = Caffeine.newBuilder()//指定初始大小.initialCapacity(1000).build();
和HashMap
类似,通过指定一个初始大小,减少扩容带来的性能损耗。这个值也不宜过大,浪费内存。
在这里,我们提出第二个问题:这个初始大小,影响那些存储参数呢?
4. 指定Key, Value为非强引用类型
Cache<String, Object> cache = Caffeine.newBuilder()// 设置 key 为 WeakReference.weakKeys().build();
cache = Caffeine.newBuilder()// 设置 key 为 WeakReference.weakKeys()// 设置 value 为 WeakReference.weakValues().build();
cache = Caffeine.newBuilder()// 设置 key 为 WeakReference.weakKeys()// 设置 value 为 SofReference.softValues().build();
对于 Java 中的 StrongReference,WeakReference,SoftReference,可以参考我的另外一篇文章:JDK核心JAVA源码解析(3) - 引用相关
在这里简单归纳下:
- StrongReference:强引用就是指在程序代码之中普遍存在的,一般的new一个对象并赋值给一个对象变量,就是一个强引用;只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。
- SoftReference:软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
- WeakReference:用来描述非必须的对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示
Caffeine 中的 Key,可以是 WeakReference,但是目前不能指定为 SoftReference,所以我们在这里提出第三个问题,为什么 Key 不能指定为 SoftReference,SoftReference 为何被区别对待。
设置 Key 和 Value 的 Reference 类型,也是一种限制大小的方式,但是限制比较多:
- 使用 weakKeys 就不能使用 Writer (这里提出第四个问题,为什么 weakKeys 和 Writer 不能同时使用)
- 使用 weakValues 或者 softValues 就不能使用异步缓存 buildAsync(这里提出第五个问题,为什么使用 weakValues 或者 softValues 就不能使用异步缓存)
一般通过 maximumSize 还有 maximumWeight 就能满足我们的需求。
设置过期时间相关
1. 自定义过期
Cache<String, Order> cache = Caffeine.newBuilder().expireAfter(new Expiry<String, Order>() {@Override//设置 Entry 创建后的过期时间//这里设置为 60s 后过期public long expireAfterCreate(@NonNull String key, @NonNull Order value, long currentTime) {return 1000 * 1000 * 1000 * 60;}@Override//设置 Entry 更新后的过期时间//这里返回 currentDuration 表示永远不过期public long expireAfterUpdate(@NonNull String key, @NonNull Order value, long currentTime, @NonNegative long currentDuration) {return currentDuration;}@Override//设置 Entry 读取后的过期时间//这里设置为 Order 的 createTime 的 60s 后过期public long expireAfterRead(@NonNull String key, @NonNull Order value, long currentTime, @NonNegative long currentDuration) {return 1000 * 1000 * 1000 * 60 - (System.currentTimeMillis() - value.createTime()) * 1000;}}).build();
通过实现 Expiry 接口,设置过期策略。这个接口主要包括三个值:
- Entry 创建后的过期时间:参数为 Entry 的 Key 还有 Value,以及 Entry 创建时间。需要返回的是这个 Entry 的生育过期时间,单位是 nanoSeconds
- Entry 更新后的过期时间:参数为 Entry 的 Key 还有 Value,以及当前时间(并不是系统当前时间,而是 Ticker 里面的当前时间,如果需要获取系统当前时间需要自己手动获取)和当前剩余的过期时间。需要返回的是这个 Entry 的剩余过期时间,单位是 nanoSeconds。如果永远不过期,可以返回 currentDuration 表示剩余时间永远不变,永远不过期。
- Entry 读取后的过期时间:参数为 Entry 的 Key 还有 Value,以及当前时间(并不是系统当前时间,而是 Ticker 里面的当前时间,如果需要获取系统当前时间需要自己手动获取)和当前剩余的过期时间。需要返回的是这个 Entry 的剩余时间,单位是 nanoSeconds。如果永远不过期,可以返回 currentDuration 表示剩余时间永远不变,永远不过期。
这个配置与接下来的 expireAfterWrite 和 expireAfterAccess 互斥。不能同时配置
** 2. 设置写入以及更新后过期**
Cache<String, Object> cache = Caffeine.newBuilder()//写入或者更新1分钟后,缓存过期并失效.expireAfterWrite(1, TimeUnit.MINUTES).build();
这个配置与上面的 expireAfter 互斥,不能同时配置
** 3. 设置操作后过期**
Cache<String, Object> cache = Caffeine.newBuilder()//写入或者更新或者读取1分钟后,缓存过期并失效.expireAfterAccess(1, TimeUnit.MINUTES).build();
这个配置与上面的 expireAfter 互斥,不能同时配置
LoadingCache 相关
** 1. 生成LoadingCache **
Cache<String, Object> cache = Caffeine.newBuilder()//使用 CacheLoader 初始化.build(key -> {return loadFromDB(key);});
当 Key 不存在或者已过期时,会调用 CacheLoader 重新加载这个 Key。那么,这里要提出下面这些问题:
- Key 是否可以为 Null,为什么
- 调用 CacheLoader 的时候,如果有异常会怎样
2. 设置定时重新加载时间
Cache<String, Object> cache = Caffeine.newBuilder()//设置在写入或者更新之后1分钟后,调用 CacheLoader 重新加载.refreshAfterWrite(1, TimeUnit.MINUTES)//使用 CacheLoader 初始化.build(key -> {return loadFromDB(key);});
注意设置了这个配置,就只能通过build(CacheLoader)
来生成 LoadingCache,不能生成普通的 Cache 了
额外配置
1. 统计记录相关
Cache<String, Object> cache = Caffeine.newBuilder()//打开数据采集.recordStats().build();
Cache<String, Object> cache = Caffeine.newBuilder()//自定义数据采集器.recordStats(() -> new StatsCounter() {@Overridepublic void recordHits(@NonNegative int count) {}@Overridepublic void recordMisses(@NonNegative int count) {}@Overridepublic void recordLoadSuccess(@NonNegative long loadTime) {}@Overridepublic void recordLoadFailure(@NonNegative long loadTime) {}@Overridepublic void recordEviction() {}@Overridepublic void recordEviction(@NonNegative int weight) {}@Overridepublic void recordEviction(@NonNegative int weight, RemovalCause cause) {}@Overridepublic @NonNull CacheStats snapshot() {return null;}
}).build();
这里我们提出两个问题:
- 默认的数据采集是否会影响性能
- 数据采集都会采集哪些数据
2. 某个 Entry 过期被移除后的回调
Cache<String, Object> cache = Caffeine.newBuilder().removalListener((key, value, cause) -> {log.info("{}, {}, {}", key, value, cause);}).build();
回调里面有三个参数,包括 Entry 的 Key, Entry 的 Value 以及移除原因 cause。这个原因是一个枚举类型:
public enum RemovalCause {EXPLICIT {@Override public boolean wasEvicted() {return false;}},REPLACED {@Override public boolean wasEvicted() {return false;}},COLLECTED {@Overridepublic boolean wasEvicted() {return true;}},EXPIRED {@Overridepublic boolean wasEvicted() {return true;}},SIZE {@Overridepublic boolean wasEvicted() {return true;}};
}
这里再提出一个问题:失效原因究竟对应哪些 API 的操作导致的失效?
3. 缓存主动更新其他存储或者资源
我们还可以通过设置 Writer,将对于缓存的更新,作用于其他存储,例如数据库:
Cache<String, Object> cache = Caffeine.newBuilder().writer(new CacheWriter<String, Object>() {@Overridepublic void write(@NonNull String key, @NonNull Object value) {//缓存更新时(包括创建和修改,不包括load),回调这里//数据库更新db.upsert(key, value);}@Overridepublic void delete(@NonNull String key, @Nullable Object value, @NonNull RemovalCause cause) {//缓存失效时(包括任何原因的失效),回调这里//数据库更新db.markAsDeleted(key, value);}}).build();
那么就引出了如下几个问题:
- 如果回调发生异常,会怎么处理?
- 具体哪些 API 会引发 write,哪些会引发 delete
异步缓存
1. 生成异步缓存
AsyncCache<String, Object> cache = Caffeine.newBuilder()//生成异步缓存.buildAsync();
这种缓存,获取的 Value 都是一个 CompletableFuture
。
**2. 生成异步 LoadingCache **
AsyncCache<String, Object> cache = Caffeine.newBuilder()//生成异步缓存.buildAsync(key -> {return loadFromDB(key);});
3. 设置异步任务线程池
AsyncCache<String, Object> cache = Caffeine.newBuilder().executor(new ForkJoinPool(10))//生成异步缓存.buildAsync();
这里我们提出如下问题:
- 异步缓存里面,哪些操作是异步的?
- 这些异步任务,执行的线程池默认是哪个?
- 异步任务有异常,如何处理?
到这里我们基本把创建说完了,接下来看一下使用这些缓存:
Cache<String, String> syncCache = Caffeine.newBuilder().build();
//加入缓存
syncCache.put(key, value);
//批量加入
syncCache.putAll(keyValueMap);
//读取缓存,如果不存在,则执行后面的mappingFunction读取并放入缓存
syncCache.get(key, k -> {return readFromOther(k);
});
//批量读取
syncCache.getAll(keys, ks -> {return readFromOther(k);
});
//获取缓存配置信息,以及其他维度的信息
Policy<String, String> policy = syncCache.policy();
//获取统计信息,前提是必须打开统计
CacheStats stats = syncCache.stats();
//获取某个key,如果不存在则返回null
syncCache.getIfPresent(key);
//将map转换为map,对map的修改会影响缓存
ConcurrentMap<@NonNull String, @NonNull String> map = syncCache.asMap();
//让某个key生效
syncCache.invalidate(key);
//让所有key失效
syncCache.invalidateAll();
//批量失效
syncCache.invalidateAll(keys);
//估计大小
@NonNegative long estimatedSize = syncCache.estimatedSize();
//等待过期清理任务完成,让缓存处于一个稳定状态
syncCache.cleanUp();
这里只提了同步缓存,异步缓存的 API 类似,只是取值变成了 CompletableFuture
包装的
接下来的章节,我们会深入研究 Caffeine 的源代码和实现原理及思想
Java本地缓存框架系列-Caffeine-1. 简介与使用相关推荐
- java 本地缓存框架_5个强大的Java分布式缓存框架推荐
在开发中大型Java软件项目时,很多Java架构师都会遇到数据库读写瓶颈,如果你在系统架构时并没有将缓存策略考虑进去,或者并没有选择更优的 缓存策略,那么到时候重构起来将会是一个噩梦.本文主要是分享了 ...
- 本地缓存框架:Caffeine Cache
1. Caffine Cache 在算法上的优点-W-TinyLFU 说到优化,Caffine Cache到底优化了什么呢?我们刚提到过LRU,常见的缓存淘汰算法还有FIFO,LFU: 1.FIFO: ...
- Java高性能本地缓存框架Caffeine
文章目录 Java高性能本地缓存框架Caffeine 如何使用 缓存加载 手动加载 自动加载 手动异步加载 自动异步加载 过期策略 基于大小 基于时间 基于引用 Caffeine.weakKeys() ...
- Caffeine Cache~高性能 Java 本地缓存之王
前面刚说到Guava Cache,他的优点是封装了get,put操作:提供线程安全的缓存操作:提供过期策略:提供回收策略:缓存监控.当缓存的数据超过最大值时,使用LRU算法替换.这一篇我们将要谈到一个 ...
- 本地缓存性能之王Caffeine
前言 随着互联网的高速发展,市面上也出现了越来越多的网站和app.我们判断一个软件是否好用,用户体验就是一个重要的衡量标准.比如说我们经常用的微信,打开一个页面要十几秒,发个语音要几分钟对方才能收到. ...
- java本地缓存_java缓存——(五)LocalCache本地缓存分享
LocalCache本地缓存分享 前言 一.本地缓存应用场景 二.java本地缓存标准 三.java开源缓存框架 四.LocalCache实现 结束语 前言 本次分享探讨java平台的本地缓存,是指占 ...
- 重新认识下JVM级别的本地缓存框架Guava Cache——优秀从何而来
Guava Cache初识 Guava是Google提供的一套JAVA的工具包,而Guava Cache则是该工具包中提供的一套完善的JVM级别的高并发缓存框架.其实现机制类似ConcurrentHa ...
- 5个强大的Java分布式缓存框架推荐
2019独角兽企业重金招聘Python工程师标准>>> 在开发中大型Java软件项目时,很多Java架构师都会遇到数据库读写瓶颈,如果你在系统架构时并没有将缓存策略考虑进去,或者并没 ...
- java gui狼_5个强大的Java分布式缓存框架
在开发中大型Java软件项目时,很多Java架构师都会遇到数据库读写瓶颈,如果你在系统架构时并没有将缓存策略考虑进去,或者并没有选择更优的缓存策略,那么到时候重构起来将会是一个噩梦.本文主要是分享了5 ...
最新文章
- 如何锁门_保安巡查时发现住户未锁门,应该怎么办?记住这九点!
- Python读CSV数据
- thinkphp3.2 验证码生成和点击刷新验证码
- curd boy 入门
- 【Get 以太坊技能】CentOS 7 Geth 搭建私链
- vue路由传参的三种基本方式 - 流年的樱花逝 - SegmentFault 思否
- 如何借助Kubernetes实现持续的业务敏捷性
- idea 2018.1 创建springboot开启找回Run Dashboard
- linux内核升级写入不了,解决linux内核升级后不能重启系统的故障
- [Unity] 使用 Visual Effect Graph 制作射击枪焰特效
- 云服务器 ECS 建站教程:部署Linux主机管理系统WDCP
- 触摸传感器的电路图符号_如何看懂汽车电路常用图形符号,看完这篇文章就懂了...
- Eclipse SQL Explorer
- PowerBI Report Server借助Wap与ADFS实现集成一
- foxmail 登陆126邮箱
- 《最好的告别》:如何优雅地走向生命终点
- Ubuntu python3安装pandas【问题解决】
- matlab自定义函数的使用方法,Matlab自定义函数的几种方法
- 阿里云centos7安装mysql
- ssh登录或者scp传文件给远程主机起别名