-
Spring boot Caffeine缓存(一)——CacheManager与配置文件
-
Spring boot Caffeine缓存(二)——Cache、LoadingCache
-
Spring boot Caffeine缓存(三)——使用注解
前边文章主要介绍了下Caffeine以及CacheManager,这里说下Caffeine的Cache和LoadingCache的配置及用法。
Cache
先来看下Cache的接口代码
@ThreadSafe
public interface Cache<K, V> {
/**
* 从cache中获取值,如果没有直接返回null
*/
@Nullable
V getIfPresent(@Nonnull Object key);
/**
* 从cache中获取值,如果没有则通过给出的Function来加载值
* 如果Function返回的值不为null会被放到缓存中,这个跟后边要讲到的CacheLoad很像
*/
@Nullable
V get(@Nonnull K key, @Nonnull Function<? super K, ? extends V> mappingFunction);
/**
* 按照给定的key返回一个map类型的集合
*/
@Nonnull
Map<K, V> getAllPresent(@Nonnull Iterable<?> keys);
/**
* 存值
*/
void put(@Nonnull K key, @Nonnull V value);
/**
* 批量存放
*/
void putAll(@Nonnull Map<? extends K,? extends V> map);
/**
* 将一个key值从缓存中清除,在方法级别有个@CacheEvict注解,其中有个evict,evict的实现也是调用的invalidate,他们最底层的实现是remove
*/
void invalidate(@Nonnull Object key);
void invalidateAll(@Nonnull Iterable<?> keys);
void invalidateAll();
/**
* 获取当前缓存中的大概存放值,这个数字不一定准确,因为它可能会包含已经过期/失效的值
*/
@Nonnegative
long estimatedSize();
/**
* 当前缓存的一些记录,这块需要自己看下具体值才能了解,下边争取列出来这块
*/
@Nonnull
CacheStats stats();
/**
* 将集合转换为一个ConcurrentMap
*/
@Nonnull
ConcurrentMap<K, V> asMap();
/**
* 清除缓存中一些过期的值
*/
void cleanUp();
/**
* 设定策略,这个后边我尽量也给列出来一块吧(如果记得)
*/
@Nonnull
Policy<K, V> policy();
}
Caffeine的Cache提供了这些方法。接下来说下代码中设置Cache,cache的配置方式有很多,或者说它支持多种策略,这个我介绍下自己学习时配置过的几种。
1、Weight
基于权重
/**
* maximumWeight基于Window TinyLfu策略(类似LRU:最近最少使用原则)丢弃缓存
* */
@Bean("Weight")
public Cache weightCache(){
return Caffeine.newBuilder()
.recordStats()
//maximumWeight与weigher结合使用,当weigher中的权重值(weigher权重的初始值 * 缓存个数)超过maximumWeight的值时,新增记录的时候删除weigher值最小的缓存
.maximumWeight(3)
//这里记录了一个键值对的权重
.weigher((k,v)->1)
.build();
}
这里的recordStats就是设置Cache记录状态,具体stats信息看下图
显示的信息很清楚,不多做解释。
Weight这块的重点是maximumWeight和weigher这块。我设置的每个key,value存储的比重是1,如果存储的所有值的比重超过maximumWeight的值3的时候,再向该缓存中新增数据就会启动淘汰策略。使用Window TinyLfu算法来计算出最应该被淘汰的值,关于这块算法的有兴趣可以单独了解下,不在本文的介绍范围内。
关于weigher((k,v)->1)这块可以根据key或者value来自定义权重,如下
private int get(Object k,Object v){
if (k.equals("kun")){
return 2;
}
return 1;
}
这时weigher引用这个方法
.weigher((k,v)->get(k,v))
这样就可以更灵活的使用权重。
说明 自己定义方法设置权重的我没测试,如果自己使用的话建议测试后再使用。
2、Size
@Bean(name = "Size")
public Cache sizeCache(){
return Caffeine.newBuilder()
.initialCapacity(5)
.maximumSize(10)
.build();
}
这块简单容易理解,initialCapacity缓存的初始大小,maximumSize最大可存储量,当超过最大值的时候就启动缓存值淘汰。
3、Expiry
/**
* Expiry自定义按时间失效方法
* */
@Bean("Expiry")
public Cache expiryCache(){
return Caffeine.newBuilder()
.expireAfter(new Expiry<Object, Object>() {
@Override
public long expireAfterCreate(Object key, Object value, long currentTime){
return currentTime;
}
@Override
public long expireAfterUpdate(Object key, Object value, long currentTime, long currentDuration){
return currentDuration;
}
@Override
public long expireAfterRead(Object key, Object value, long currentTime, long currentDuration){
return currentDuration;
}
})
.build();
}
根据自己的需求设置过期时间,这里作者给出了三个接口,并没有给出实现。今天我在看这块代码的时候突然想到可以在这里做文章来支持单个key设置过期时间的,以后研究下如果可以的话再补充。
4、 refreshAfterWrite
@Bean("refreshAfterWrite")
public Cache refreshAfterWriteCache(){
return Caffeine.newBuilder()
.recordStats()
.refreshAfterWrite(10,TimeUnit.SECONDS)
.build(key -> caffeineService.getCacheService(String.valueOf(key)));
}
refreshAfterWrite必须设置cacheLoad方法,我这里是用lamda表达式调用自己的一个方法来加载新值的。
5、writer
/**
* writer的监控是同步执行的
* */
@Bean("writer")
public Cache writerCache(){
return Caffeine.newBuilder()
.recordStats()
.expireAfterWrite(5,TimeUnit.SECONDS)
.writer(new CacheWriter<Object, Object>() {
@Override
public void write(Object key, Object value){
logger.info("This is writerCache's write");
//如果有缓存新增,这里的方法将被执行
//写操作是阻塞的,写的时候读数据会返回原有值
}
@Override
public void delete(Object key, Object value, RemovalCause cause){
logger.info("This is writerCache's delete");
//如果有缓存删除(到期等),这里的方法将被执行
}
})
.build(key -> caffeineService.getCacheService(String.valueOf(key)));
}
writer其实是提供了两个监听机制,缓存被创建或修改的时候会触发write操作,删除的时候触发delete方法,这里可以作为状态修改的一种监听(下面有专门的监听配置)。
6、RemovalListener
/**
* RemovalListener的方法是异步执行的
* */
@Bean("RemovalListener")
public Cache removalListenerCache(){
return Caffeine.newBuilder()
.recordStats()
.refreshAfterWrite(5,TimeUnit.SECONDS)
.removalListener((key, value, cause) -> myRemovalListener(key, value, cause))
.build(cacheLoader);
// .build(key -> caffeineService.getCacheService(String.valueOf(key)));
}
private void myRemovalListener(Object key, Object value, RemovalCause cause){
logger.info("key = {}",key);
logger.info("value = {}",value);
// int i = 10/0;
logger.info("This is myRemovalListener, removal key = {}, value = {}, cause = {}",key,value,cause);
}
RemovalListener是针对缓存被移除的时候做监听的,这点跟writer的delete功能相同,但是writer提供了新增和修改的监控,看起来这个并没有writer的功能强大。以后使用的时候要多对这两个作对比。
还有两个expireAfterWrite和expireAfterAccess没啥好说的,大家看下代码就清楚了。
/**
* 根据写入的时间设置过期时间
* */
@Bean("expireAfterWrite")
public Cache expireAfterWriteCache(){
return Caffeine.newBuilder()
.recordStats()
.expireAfterWrite(10,TimeUnit.SECONDS)
.build();
}
/**
* 根据访问的时间设置过期时间
* */
@Bean("expireAfterAccess")
public Cache expireAfterAccessCache(){
return Caffeine.newBuilder()
.recordStats()
.expireAfterAccess(10,TimeUnit.SECONDS)
.build();
}
LoadingCache
LoadingCache直接定义一个缓存,在项目中可以直接拿来存取数据,同时还可以引入CacheLoad加载数据。
LoadingCache是个接口,继承了Cache。
@ThreadSafe
public interface LoadingCache<K, V> extends Cache<K, V> {
@Nullable
V get(@Nonnull K key);
@Nonnull
Map<K, V> getAll(@Nonnull Iterable<? extends K> keys);
void refresh(@Nonnull K key);
}
相较于Cache它增加了上面三个防范。
1、get
可以直接通过key获取缓存中的数据,不需要添加function,如果cache中没有存该key,会通过配置的CacheLoad加载key的值。
2、getAll
通过一组key从cache中得到一组map类型的数据,这个没啥说的。
3、refresh
这是通过cacheLoad来重新加载key的值。
LoadingCache对Cache做了一个升级,提供了更加强大的功能,看一段简单的代码。
@Autowired
private CacheLoader cacheLoader;
/**
* 用来加载数据
* */
@Bean(name = "LoadingCache")
public LoadingCache cacheManagerWithAsyncCacheLoader(){
logger.info("cacheManagerWithCacheLoading" );
LoadingCache loadingCache = Caffeine.newBuilder()
.recordStats()
.build(cacheLoader);
return loadingCache;
}
这块看起来跟上面的cache很像。准确来说上边的cache中build有lamda表达式或者cacheLoader的都是LoadingCache。
LoadingCache提供了缓存的加载方法,而Cache只是做一个key,value的存放等,更像一个map。使用的时候可以根据自己的需求来定义。