Spring boot Caffeine缓存(二)——Cache、LoadingCache

  • Post author:
  • Post category:其他



  1. Spring boot Caffeine缓存(一)——CacheManager与配置文件

  2. Spring boot Caffeine缓存(二)——Cache、LoadingCache

  3. 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。使用的时候可以根据自己的需求来定义。



版权声明:本文为qq_35981283原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。