如图,Caffeine是当前最优秀的内存缓存框架,不论读还是写的效率都远高于其他缓存,而且在Spring5开始的默认缓存实现就将Caffeine代替原来的Google Guava
基础使用
1 | java复制代码<!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine --> |
手动创建缓存
1 | java复制代码 Cache<Object, Object> cache = Caffeine.newBuilder() |
自动创建缓存
1 | java复制代码LoadingCache<String, String> loadingCache = Caffeine.newBuilder() |
异步获取缓存
1 | java复制代码AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder() |
PS:可以使用.executor()
自定义线程池
记录命中数据
1 | java复制代码LoadingCache<String, String> cache = Caffeine.newBuilder() |
PS:会影响性能,生产环境下建议不开启
淘汰策略
先了解一下常见的淘汰策略
- LRU 最近最少使用,淘汰最长时间没有被使用的页面。
- LRU 最不经常使用,淘汰一段时间内,使用次数最少的页面
- FIFO 先进先出
LRU的优点:LRU相比于 LFU 而言性能更好一些,因为它算法相对比较简单,不需要记录访问频次,可以更好的应对突发流量。
LRU的缺点:虽然性能好一些,但是它通过历史数据来预测未来是局限的,它会认为最后到来的数据是最可能被再次访问的,从而给与它最高的优先级。有些非热点数据被访问过后,占据了高优先级,它会在缓存中占据相当长的时间,从而造成空间浪费。
LFU的优点:LFU根据访问频次访问,在大部分情况下,热点数据的频次肯定高于非热点数据,所以它的命中率非常高。
LFU的缺点:LFU 算法相对比较复杂,性能比 LRU 差。有问题的是下面这种情况,比如前一段时间微博有个热点话题热度非常高,就比如那种可以让微博短时间停止服务的,于是赶紧缓存起来,LFU 算法记录了其中热点词的访问频率,可能高达十几亿,而过后很长一段时间,这个话题已经不是热点了,新的热点也来了,但是,新热点话题的热度没办法到达十几亿,也就是说访问频次没有之前的话题高,那之前的热点就会一直占据着缓存空间,长时间无法被剔除。
而Caffeine 采用W-TinyLFU淘汰算法,结合LRU与LFU达到更佳的命中率与性能,具体参考:
www.cnblogs.com/zhaoxinshan…
4种淘汰方式与例子
Caffeine有4种缓存淘汰设置
- 大小 (会使用上面说到的W-TinyLFU算法进行淘汰)
- 权重 (大小与权重 只能二选一)
- 时间
- 引用 (不常用,本文不介绍)
例子:
1 | java复制代码import com.github.benmanes.caffeine.cache.Cache; |
另外还有一个refreshAfterWrite()
表示x秒后自动刷新缓存可以配合以上的策略使用
1 | java复制代码private static int NUM = 0; |
最佳实践
在实际开发中如何配置淘汰策略最优呢,根据我的经验常用的还是以大小淘汰为主
实践1
配置:设置 maxSize、refreshAfterWrite,不设置 expireAfterWrite/expireAfterAccess
优缺点:因为设置expireAfterWrite当缓存过期时会同步加锁获取缓存,所以设置expireAfterWrite时性能较好,但是某些时候会取旧数据,适合允许取到旧数据的场景
实践2
配置:设置 maxSize、expireAfterWrite/expireAfterAccess,不设置 refreshAfterWrite
优缺点:与上面相反,数据一致性好,不会获取到旧数据,但是性能没那么好(对比起来),适合获取数据时不耗时的场景
与Srping Boot集成
1 | xml复制代码<!--缓存--> |
配置方式有2种
- yml 不推荐,因为淘汰策略是公用的,不可以给每一个缓存配置不一样的淘汰策略,此处不演示
- 使用
@Configuration
类
此处演示第二种配置方式
- 开启缓存
@EnableCaching
1 | java复制代码@Configuration |
直接可以使用Spring 缓存注解,@Cacheable、@CacheEvict、@CachePut
等,此处也不作详解
1 | java复制代码@Service |
配合Redis做二级缓存
缓存的解决方案一般有三种
- 本地内存缓存,如Caffeine、Ehcache; 适合单机系统,速度最快,但是容量有限,而且重启系统后缓存丢失
- 集中式缓存,如Redis、Memcached; 适合分布式系统,解决了容量、重启丢失缓存等问题,但是当访问量极大时,往往性能不是首要考虑的问题,而是带宽。现象就是 Redis 服务负载不高,但是由于机器网卡带宽跑满,导致数据读取非常慢
- 第三种方案就是结合以上2种方案的二级缓存应运而生,以内存缓存作为一级缓存、集中式缓存作为二级缓存
市面上已有成熟的框架,开源中国官方开源的工具:J2Cache
大致原理就是这样
配合spring boot使用参考:
1 | xml复制代码<!-- 可以不引入caffine,j2cache默认使用2.x版本 --> |
bootstrap.yml
1 | yml复制代码j2cache: |
新增2个properties配置文件(不支持yml):
j2cache-test.properties
1 | ini复制代码#J2Cache configuration |
caffeine.properties:
1 | ini复制代码######################################### |
完毕。以上配置是本人整理后的配置,具体使用还需要查阅官方文档。
另外对二级缓存原理有兴趣建议去看另一个项目,源码相比j2cache写得比较容易理解,属于阉割版的二级缓存:github.com/pig-mesh/mu…
本文转载自: 掘金