Java guava框架LoadingCache及CacheBuilder本地小容量缓存框架总结
作者:极光雨雨
Guava Cache本地缓存框架介绍
主要是一种将本地数据缓存到内存中,但数据量并不能太大,否则将会占用过多的内存,虽然框架本身已经做了相当的数据回收,但还是不可以滥用,需要符合以下优点场景,才是合适使用,访问内存的速度快于访问 redis 等数据库。
有点以及需求场景:
- 对性能有非常高的要求
- 愿意消耗一些内存空间来提升速度
- 预计到某些键会被多次查询
- 缓存中存放的数据总量不会超出内存容量
关键点是:有频繁访问的数据,且这些数据本身占用内存量很少,将这些数据存储到该缓存框架中管理以提供性能。
提供的优势能力
- 缓存可以设置过期时间,并提供数据过多时的淘汰机制
- 是线程安全的,支持并发读入和写入
- 缓存获取不到时可以从数据源获取并加入到缓存中,GuavaCache 可以使用 CacheLoader 的load 方法控制,对同一个key,只允许一个请求去读源并回填缓存,其他请求阻塞等待
- 可以查看缓存的加载获取信息等
使用以及方法
Maven 依赖:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>20.0</version> </dependency>
最简单的构建案例:
Cache<String, Object> cache = CacheBuilder.newBuilder().build(); cache.put("aaa", 156484);
与Map类似,获取时使用get() 方法即可获取到放入其中的数据。
通过 CacheBuilder 创建 Cache 对象,存储类似于Map 构建方法为链式构造,类似于 builder 建造者模式,返回均为当前对象本身,调用 build 方法后结束构造。
CacheBuilder.newBuilder() 后的一些构建参数方法介绍:
initialCapacity:缓存的初始数据容量大小,一般要贴合实际否则会造成资源浪费
maximumSize:缓存中可包含最大 entry 数量,超过数量限制后淘汰 entry,接近最大值时淘汰不常用数据,设置为 0 时为不使用缓存的场景,用于测试数据加载
过期时间设置
expireAfterAccess:数据写入后被访问对象多久没被访问认为过期
expireAfterWrite: 数据被写入到缓存后一直未更新多久后过期
可以如下写:
Cache<String, Object> cache = CacheBuilder.newBuilder() .initialCapacity(5) .maximumSize(10) .expireAfterWrite(10, TimeUnit.MINUTES).build(); cache.put("154", "djawbdawd");
过期时间单位通过 TimeUnit 后的控制,时分秒均可
弱软引用 (weakKeys, weakValues, softValues)
weakKeys
将缓存中的key设置成weakKey模式。
默认情况下,会使用“强关系”来保存key值。当设置为weakKey时,会使用 “==” 来匹配key值。在使用weakKey的情况下,数据可能会被GC。数据被GC后,可能仍然会被size方法计数,但是对其执行read或write方法已经无效。
weakValues
将缓存中的数据设置为weakValues模式。
启用时,某些数据会被GC。默认情况下会使用“强关系”来保存key值。当设置为weakValue时,会使用 “==” 来匹配value值。数据被GC后,可能仍然会被size方法计数,但是对其执行read或write方法已经无效。
softValues
将缓存中的数据设置为softValues模式。
启用时,所有的数据都使用SoftReference类对缓存中的数据进行包裹(就是在SoftReference实例中存储真实的数据)。使用SoftReference包裹的数据,会被全局垃圾回收管理器托管,按照LRU的原则来定期GC数据。数据被GC后,可能仍然会被size方法计数,但是对其执行read或write方法已经无效。
这些同样在 CacheBuilder.newBuilder() 后设置。
主动删除数据
当通过 builder 创建对象完成后,可以通过以下方式清除数据:
invalidateAll:清除全部数据,入参为 Iterable 类型参数,即 一般的List 集合即可,内容为所有的 key
invalidate:单一删除,入参为 key
删除监听器
即用于监控缓存中的数据,当数据被删除时会被触发
类似如下代码:
RemovalListener<String, String> listener = new RemovalListener<String, Object>() { public void onRemoval(RemovalNotification<String, String> notification) { // notification.getKey(); // 当前删除对象的 key // notification.getValue(); // 当前删除对象的 value } }; Cache<String, Object> cache = CacheBuilder.newBuilder() .maximumSize(5) // 添加移除监听器 .removalListener(listener) .build();
值得注意的是,这里的删除不仅是主动删除,当达到容量上限或者过期或由于其他策略导致数据消失时也认为是删除
一般cache 主动加载数据
即当缓存中获取不到指定 key 对应的 value 时,需要主动去其他途径获取:
写法类似如下,需要提供获取的实现方法:
Cache<String, Object> cache = CacheBuilder.newBuilder() .initialCapacity(5) .maximumSize(10) .expireAfterWrite(10, TimeUnit.MINUTES).build(); cache.put("154", "djawbdawd"); try { cache.get("aaa", new Callable<Object>() { @Override public Object call() throws Exception { // 具体如何获取 // 方法的返回值即为 当前key 对应的value 并将会加入到缓存中 return null; } }); } catch (ExecutionException e) { e.printStackTrace(); }
需要在 Callable 内的方法重写获取实际数据的方法,方法的返回值即为 当前key 对应的value 并将会加入到缓存中,同时如果存在多个线程同时获取同一个不存在的 key,那么将只有一个被执行,其他需要等待。且一个线程获取后,其他线程将也可以获取这些数据。
统计信息
创建cache 对象时调用**.recordStats()** 后将开启统计,可以通过 cache.stats() 获取缓存
例如:
Cache<String, Object> cache = CacheBuilder.newBuilder() .initialCapacity(5) .maximumSize(10) .recordStats() .expireAfterWrite(10, TimeUnit.MINUTES).build(); cache.put("154", "djawbdawd"); try { cache.get("aaa", new Callable<Object>() { @Override public Object call() throws Exception { // ....... return null; } }); } catch (ExecutionException e) { e.printStackTrace(); } cache.stats();
较常用的 LoadingCache
LoadingCache 是 Cache的子接口 ,相比于Cache,当从LoadingCache中读取一个指定key的记录时,如果该记录不存在,则LoadingCache可以自动执行加载数据到缓存的操作,定义时需要重写加载方法。
LoadingCache接口的定义如下:
CacheLoader<String, String> loader = new CacheLoader<>() { // 加载没有的数据的方法 public String load(String key) throws Exception { // ............. return null; } }; LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder() .maximumSize(5) .build(loader);
或如下:
LoadingCache<String, Object> infoItemCache = CacheBuilder.newBuilder() .initialCapacity(5) .maximumSize(10) .expireAfterAccess(3, TimeUnit.MINUTES).build(new CacheLoader<String, Object>() { // 使用 LoadingCache 重写加载方法, 重写后当无法从缓存中获取到key 对应的值时将执行重写的load 方法,且返回值将成为入参key 对应的Value并存储 @Override public Object load(String s) throws Exception { // ............ return null; } });
可以自定义简单工具如下:
import com.dtdream.dthink.dtalent.dmall.dataresource.catalog.model.metadata.MetadataBlock; import com.dtdream.dthink.dtalent.dmall.dataresource.catalog.service.catalog.ICatalogTemplateService; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class MyCache { /** * 初始化缓存容量大小 */ private static final int INITIAL_CAPACITY = 5; /** * 缓存中可包含最大 entry 数量,超过数量限制后淘汰 entry,接近最大值时淘汰不常用数据,设置为 0 时为不使用缓存的场景,用于测试数据加载 */ private static final long MAXIMUM_SIZE = 10; /** * 放入缓存后指定时间内没有被访问将过期 */ private static final long EXPIRE_AFTER_ACCESS = 5; /** * cache 子接口, 缓存对象 */ private static LoadingCache<String, Object> infoItemCache; // 初始化缓存对象 static { infoItemCache = CacheBuilder.newBuilder().initialCapacity(INITIAL_CAPACITY).maximumSize(MAXIMUM_SIZE) .expireAfterAccess(EXPIRE_AFTER_ACCESS, TimeUnit.MINUTES).build(new CacheLoader<String, Object>() { // 使用 LoadingCache 重写加载方法, 重写后当无法从缓存中获取到key 对应的值时将执行重写的load 方法,且返回值将成为入参key 对应的Value并存储 @Override public Object load(String s) throws Exception { // ............... return null; } }); } /** * 从缓存中获取数据,若不存在则重新查询 * @return */ public static Object getFromCache(String key) throws ExecutionException { Object data = infoItemCache.get(key); return data; } }
若需要从Spring 获取Bean 服务,则可以通过 Spring 的 applicationConext 去获取
到此这篇关于Java guava框架 LoadingCache,CacheBuilder 本地小容量缓存框架学习以及总结的文章就介绍到这了,更多相关Java guava框架 LoadingCache内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!