原文
使用Guava cache构建本地缓存 - sameLuo的个人空间 - OSCHINA
Guava Cache -- Java 应用缓存神器 - 云+社区 - 腾讯云
Google Guava缓存
最近需要用到缓存来存放临时数据,又不想采用Redis,Java自带的Map功能太少,发现Google的Guava提供的Cache模块功能很强大,于是选择使用它。
本地缓存
本地缓存作用就是提高系统的运行速度,是一种空间换时间的取舍。它实质上是一个做key-value查询的字典,但是相对于我们常用HashMap它又有以下特点:
1.并发性:由于目前的应用大都是多线程的,所以缓存需要支持并发的写入。
2.过期策略:在某些场景中,我们可能会希望缓存的数据有一定“保质期”,过期策略可以固定时间,例如缓存写入10分钟后过期。也可以是相对时间,例如10分钟内未访问则使缓存过期(类似于servlet中的session)。在java中甚至可以使用软引用,弱引用的过期策略。
3.淘汰策略:由于本地缓存是存放在内存中,我们往往需要设置一个容量上限和淘汰策略来防止出现内存溢出的情况。
缓存应当具备的属性为:
1、能够配置缓存的大小,保持可控的Memory。
2、适应多种场景的数据expire策略。
3、在高并发情况下、能够正常缓存的更新以及返回。
Guava Cache适用于:
你愿意消耗一些内存空间来提升速度。
你预料到某些键会被查询一次以上。
缓存中存放的数据总量不会超出内存容量
缓存的最大容量与淘汰策略
由于本地缓存是将计算结果缓存到内存中,所以我们往往需要设置一个最大容量来防止出现内存溢出的情况。这个容量可以是缓存对象的数量,也可以是一个具体的内存大小。在Guva中仅支持设置缓存对象的数量。
当缓存数量逼近或大于我们所设置的最大容量时,为了将缓存数量控制在我们所设定的阈值内,就需要丢弃掉一些数据。由于缓存的最大容量恒定,为了提高缓存的命中率,我们需要尽量丢弃那些我们之后不再经常访问的数据,保留那些即将被访问的数据。为了达到以上目的,我们往往会制定一些缓存淘汰策略,常用的缓存淘汰策略有以下几种:
1.FIFO:First In First Out,先进先出。
一般采用队列的方式实现。这种淘汰策略仅仅是保证了缓存数量不超过我们所设置的阈值,而完全没有考虑缓存的命中率。所以在这种策略极少被使用。
2.LRU:Least Recently Used,最近最少使用;
该算法其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
所以该算法是淘汰最后一次使用时间离当前最久的缓存数据,保留最近访问的数据。所以该种算法非常适合缓存“热点数据”。
但是该算法在缓存周期性数据时,就会出现缓存污染,也就是淘汰了即将访问的数据,反而把不常用的数据读取到缓存中。
为了解决这个问题,后续也出现了如LRU-K,Two queues,Multi Queue等进阶算法。
3.LFU:Least Frequently Used,最不经常使用。
该算法的核心思想是“如果数据在以前被访问的次数最多,那么将来被访问的几率就会更高”。所以该算法淘汰的是历史访问次数最少的数据。
一般情况下,LFU效率要优于LRU,且能够避免周期性或者偶发性的操作导致缓存命中率下降的问题。但LFU需要记录数据的历史访问记录,一旦数据访问模式改变,LFU需要更长时间来适用新的访问模式,即:LFU存在历史数据影响将来数据的“缓存污染”效用。
后续出现LFU*,LFU-Aging,Window-LFU等改进算法。
合理的使用淘汰算法能够很明显的提升缓存命中率,但是也不应该一味的追求命中率,而是应在命中率和资源消耗中找到一个平衡。
在guava中默认使用LRU淘汰算法,而且在不修改源码的情况下也不支持自定义淘汰算法。
使用Guava构建缓存
// 通过CacheBuilder构建一个缓存实例
Cache cache = CacheBuilder.newBuilder()
.maximumSize(100) // 设置缓存的最大容量
.expireAfterWrite(1, TimeUnit.MINUTES) // 设置缓存在写入一分钟后失效
.concurrencyLevel(10) // 设置并发级别为10
.recordStats() // 开启缓存统计
.build();
// 放入缓存
cache.put("key", "value");
// 获取缓存
String value = cache.getIfPresent("key");
Guava的缓存有许多配置选项,所以为了简化缓存的创建过程,使用了Builder设计模式
上面的代码演示了使用Guava创建了一个基于内存的本地缓存,并指定了一些缓存的参数,如缓存容量、缓存过期时间、并发级别等,随后通过put方法放入一个缓存并使用getIfPresent来获取它。
Cache与LoadingCache
使用CacheBuilder我们能构建出两种类型的cache,他们分别是Cache与LoadingCache。
Cache
Cache是通过CacheBuilder的build()方法构建,它是Gauva提供的最基本的缓存接口,并且它提供了一些常用的缓存api:
// 放入/覆盖一个缓存
cache.put("k1", "v1");
// 获取一个缓存,如果该缓存不存在则返回一个null值
Object value = cache.getIfPresent("k1");
// 获取缓存,当缓存不存在时,则通Callable进行加载并返回。该操作是原子
Object getValue = cache.get("k1", new Callable