Java本地缓存之Caffeine的使用

一、什么是缓存

在java中,缓存是在计算机上的一个原始数据的复制集。通俗的说就是数据的一个备份。

二、缓存的作用

就是将程序或系统经常要调用的对象或者数据存在内存中,再次调用时可以快速从内存中获取对象,不必再去创建新的
重复的实例或者不用从处理数据速度比较慢的磁盘上获取数据。

三、缓存的优点

由于内存的容量比较小,但是磁盘的容量比较大,所以一般都是把热数据(访问比较频繁的数据存放到缓存中去,这样
系统程序就能快速的访问到热点数据,然后快速做出反应,提高了程序流畅性的同时,也提高了用户的体验)

四、为什么用缓存就更快?(表面理解)

  • 内存的io速度要比磁盘的io速度快很多。(也就是说内存处理数据的速度要比磁盘处理数据的速度快很多)。
  • 基于第一点,所以缓存处理数据的速度要比数据库处理数据的速度要快很多。(因为缓存中的数据是内存io,数据库的数据是磁盘io)。

五、缓存的分类

  • 客户端缓存
  • 服务端缓存(又分为服务器本地缓存(ehcache、guava cache、Caffeine)、分布式缓存(Redis、Memcached等nosql、MangoDB)、数据库缓存)
  • 网络中缓存

六、本地缓存之Caffeine的使用

缓存填充策略

缓存的填充方式有三种,手动、同步和异步,下面分别简单介绍下每种方式使用的API:

  1. 手动加载:手动控制缓存的增删改处理,主动增加、获取以及依据函数式更新缓存;底层使用ConcurrentHashMap进行节点存储,因此get()方法是安全的。批量查找可以使用getAllPresent()方法或者带填充默认值的getAll()方法。
    /**
     * 手动填充测试
     */
    public void cacheTest(String k) {
    
    
        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(100)
                .expireAfterAccess(100L, TimeUnit.SECONDS)
                .build();

        cache.put("c1", "c1");
        //获取缓存值,如果为空,返回null
        log.info("cacheTest present: [{}] -> [{}]", k, cache.getIfPresent(k));
        //获取返回值,如果为空,则运行后面表达式,存入该缓存
        log.info("cacheTest default: [{}] -> [{}]", k, cache.get(k, this::buildLoader));
        log.info("cacheTest present: [{}] -> [{}]", k, cache.getIfPresent(k));
        //清除缓存
        cache.invalidate(k);
        log.info("cacheTest present: [{}] -> [{}]", k, cache.getIfPresent(k));
    }
    private String buildLoader(String k) {
    
    
        return k + "+default";
    }

  1. 同步加载:LoadingCache对象进行缓存的操作,使用CacheLoader进行缓存存储管理。批量查找可以使用getAll()方法。默认情况下,getAll()将会对缓存中没有值的key分别调用CacheLoader.load方法来构建缓存的值(build中的表达式)。我们可以重写CacheLoader.loadAll方法来提高getAll()的效率。
    /**
     * 同步填充测试
     */
    public void loadingCacheTest(String k) {
    
    
        //同步填充在build方法指定表达式
        LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
                .maximumSize(100)
                .expireAfterAccess(100L, TimeUnit.SECONDS)
                .build(this::buildLoader);
        
        loadingCache.put("c1", "c1");
        log.info("loadingCacheTest get: [{}] -> [{}]", k, loadingCache.get(k));
        //获取缓存值,如果为空,返回null
        log.info("loadingCacheTest present: [{}] -> [{}]", k, loadingCache.getIfPresent(k));
        //获取返回值,如果为空,则运行后面表达式,存入该缓存
        log.info("loadingCacheTest default: [{}] -> [{}]", k, loadingCache.get(k, this::buildLoader));
        log.info("loadingCacheTest present: [{}] -> [{}]", k, loadingCache.getIfPresent(k));
        loadingCache.invalidate(k);
        log.info("loadingCacheTest present: [{}] -> [{}]", k, loadingCache.getIfPresent(k));
    }
    private String buildLoader(String k) {
    
    
        return k + "+default";
    }

  1. 异步加载:
  • 自动异步加载:
    AsyncLoadingCache对象进行缓存管理,get()返回一个CompletableFuture对象,默认使用ForkJoinPool.commonPool()来执行异步线程,但是我们可以通过Caffeine.executor(Executor) 方法来替换线程池。
    /**
     * 异步填充测试
     */
    public void asyncLoadingCacheTest(String k) throws ExecutionException, InterruptedException {
    
    
        //异步加载使用Executor去调用方法并返回一个CompletableFuture。异步加载缓存使用了响应式编程模型。
        //
        //如果要以同步方式调用时,应提供CacheLoader。要以异步表示时,应该提供一个AsyncCacheLoader,并返回一个CompletableFuture。
        AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
                .maximumSize(100)
                .expireAfterAccess(100L, TimeUnit.SECONDS)
                .buildAsync(s -> this.buildLoaderAsync("123").get());

        log.info("asyncLoadingCacheTest get: [{}] -> [{}]", k, asyncLoadingCache.get(k).get());
        //获取返回值,如果为空,则运行后面表达式,存入该缓存
        log.info("asyncLoadingCacheTest default: [{}] -> [{}]", k, asyncLoadingCache.get(k, this::buildLoader).get());
    }

    private CompletableFuture<String> buildLoaderAsync(String k) {
    
    
        return CompletableFuture.supplyAsync(() -> k + "+buildLoaderAsync");
    }

  • 手动异步加载:
    /**
     * 手动异步测试
     */
    public void asyncManualCacheTest(String k) throws ExecutionException, InterruptedException {
    
    
        //异步加载使用Executor去调用方法并返回一个CompletableFuture。异步加载缓存使用了响应式编程模型。
        //
        //如果要以同步方式调用时,应提供CacheLoader。要以异步表示时,应该提供一个AsyncCacheLoader,并返回一个CompletableFuture。
        AsyncCache<String, String> asyncCache = Caffeine.newBuilder()
                .maximumSize(100)
                .expireAfterAccess(100L, TimeUnit.SECONDS)
                .buildAsync();
        //获取返回值,如果为空,则运行后面表达式,存入该缓存
        log.info("asyncManualCacheTest default: [{}] -> [{}]", k, asyncCache.get(k, this::buildLoader).get());
    }

过期策略

Caffeine的缓存清除是惰性的,可能发生在读请求后或者写请求后,比如说有一条数据过期后,不会立即删除,可能在下一次读/写操作后触发删除(类比于redis的惰性删除)。如果读请求和写请求比较少,但想要尽快的删掉cache中过期的数据的话,可以通过增加定时器的方法,定时执行cache.cleanUp()方法(异步方法,可以等待执行),触发缓存清除操作。

  • 基于大小过期:当缓存超出后,使用W-TinyLFU算法进行缓存淘汰处理
  • 基于缓存条目过期
    maximumSize()方法,参数是缓存中存储的最大缓存条目,当添加缓存时达到条目阈值后,将进行缓存淘汰操作
    /**
    * 淘汰策略-size
    */
   public void sizeTest() {
    
    
       LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
               .maximumSize(1)
               .build(this::buildLoader);

       List<String> list = Lists.newArrayList("c1", "c2", "c3");
       loadingCache.put(list.get(0), list.get(0));
       log.info("weightTest get: [{}] -> [{}]", list.get(0), loadingCache.get(list.get(0)));
       loadingCache.put(list.get(1), list.get(1));
       log.info("weightTest get: [{}] -> [{}]", list.get(1), loadingCache.get(list.get(1)));
       loadingCache.put(list.get(2), list.get(2));
       log.info("weightTest get: [{}] -> [{}]", list.get(2), loadingCache.get(list.get(2)));
       log.info("weightTest cache map:{}", loadingCache.getAll(list));
   }

  • 基于权重过期:权重大小不会决定缓存满时清楚的优先级
    weigher()方法可以指定缓存所占比重,maximumWeight()方法指定最大的权重阈值,当添加缓存超过规定权重后,进行数据淘汰
    /**
    * 淘汰策略-weight
    */
   public void weightTest() {
    
    
       LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
               .maximumWeight(10)
               .weigher((key, value) -> 5)
               .build(this::buildLoader);

       List<String> list = Lists.newArrayList("c1", "c2", "c3");
       loadingCache.put(list.get(0), list.get(0));
       log.info("weightTest get: [{}] -> [{}]", list.get(0), loadingCache.get(list.get(0)));
       loadingCache.put(list.get(1), list.get(1));
       log.info("weightTest get: [{}] -> [{}]", list.get(1), loadingCache.get(list.get(1)));
       log.info("weightTest cache map:{}", loadingCache.getAll(list));
   }

  • 基于时间过期

    (1) expireAfterAccess():缓存访问后,一定时间失效;即最后一次访问或者写入开始时计时。

    (2) expireAfterWrite():缓存写入后,一定时间失效;以写入缓存操作为准计时。

    (3) expireAfter():自定义缓存策略,满足多样化的过期时间要求。

    /**
     * 淘汰策略-time
     */
    public void timeTest() {
    
    
        //1.缓存访问后,一定时间后失效
        LoadingCache<String, String> loadingCacheOne = Caffeine.newBuilder()
                .expireAfterAccess(10L, TimeUnit.SECONDS)
                .build(this::buildLoader);

        //2.缓存写入后,一定时间后失效
        LoadingCache<String, String> loadingCacheTwo = Caffeine.newBuilder()
                .expireAfterWrite(10L, TimeUnit.SECONDS)
                .build(this::buildLoader);

        //3.自定义过期策略
        LoadingCache<String, String> loadingCacheThree = Caffeine.newBuilder()
                .expireAfter(new Expiry<Object, Object>() {
    
    
                    @Override
                    public long expireAfterCreate(@NonNull Object o, @NonNull Object o2, long l) {
    
    
                        return 0;
                    }

                    @Override
                    public long expireAfterUpdate(@NonNull Object o, @NonNull Object o2, long l, @NonNegative long l1) {
    
    
                        return 0;
                    }
                    @Override
                    public long expireAfterRead(@NonNull Object o, @NonNull Object o2, long l, @NonNegative long l1) {
    
    
                        return 0;
                    }
                })
                .build(this::buildLoader);
    }

手动删除

Caffeine中提供了手动进行缓存的删除,无需等待我们上面提到的被动的一些删除策略,使用方法如下:

    cacheOne.invalidateAll();
    cacheOne.invalidate(Object o);
    cacheOne.invalidateAll(List);

事件监听

移除事件RemovalListener是一种缓存监听事件,当key被移除的时候就会触发这个方法,可以进行一些相关联的操作。RemovalListener可以获取到key、value和RemovalCause(删除的原因)。另外RemovalListener中操作是线程池异步执行的。

    /**
     * 移除监听器
     */
    public void removeTest() throws InterruptedException {
    
    
        Cache<String, String> cacheOne = Caffeine.newBuilder()
                .maximumSize(1)
                .removalListener(
                (o, o2, removalCause) -> 
                System.out.println(o + " is " + "remove" + " reason is " + removalCause.name()))
                .build();
    }

说明:本文有些许参考之处,而且有个人观点杂糅其中,如果有大神发现不当之处,还请不吝赐告,笔者定当虚心受教,谢谢!
站在巨人的肩膀上:https://blog.csdn.net/l_dongyang/article/details/108326755

扫描二维码关注公众号,回复: 15466669 查看本文章

猜你喜欢

转载自blog.csdn.net/qq_41774102/article/details/127495276
今日推荐