首页 > 其他分享 >Caffeine学习笔记

Caffeine学习笔记

时间:2024-10-16 13:48:38浏览次数:7  
标签:缓存 stats Caffeine 笔记 System 学习 println out

作者:京东工业 孙磊

一、认识Caffeine

1、Caffeine是什么?

Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能的缓存库, 也是SpringBoot内置的本地缓存实现。

2、Caffeine提供了灵活的构造器去创建一个拥有下列特性的缓存:

•自动加载条目到缓存中,可选异步方式

•可以基于大小剔除

•可以设置过期时间,时间可以从上次访问或上次写入开始计算

•异步刷新

•keys自动包装在弱引用中

•values自动包装在弱引用或软引用中

•条目剔除通知

•缓存访问统计

3、核心类和参数

核心工具类:Caffeine是创建高性能缓存的基类。

核心参数:

maximumSize:缓存最大值

maximumWeight:缓存最大权重,权重和最大值不能同时设置

initialCapacity:缓存初始容量

expireAfterWriteNanos:在写入多少纳秒没更新后过期

expireAfterAccessNanos:在访问多少纳秒没更新后过期

refreshAfterWriteNanos:写入多少纳秒没更新后更新

二、数据加载

Caffeine提供了四种缓存添加策略

1、手动加载

public static void demo() {
    Cache<String, String> cache =
            Caffeine.newBuilder()
                    .expireAfterAccess(Duration.ofMinutes(1))
                    .maximumSize(100)
                    .recordStats()
                    .build();

    // 插入数据
    cache.put("a", "a");
    // 查询某个key,如果没有返回空
    String a = cache.getIfPresent("a");
    System.out.println(a);
    // 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回null
    String b = cache.get("b", k -> {
        System.out.println("begin query ..." + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("end query ...");
        return UUID.randomUUID().toString();
    });
    System.out.println(b);

    // 移除一个缓存元素
    cache.invalidate("a");
}

2、自动加载

public static void demo() {

        LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
                .maximumSize(100)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader() {

                    @Nullable
                    @Override
                    public Object load(@NonNull Object key) throws Exception {
                        return createExpensiveValue();
                    }

                    @Override
                    public @NonNull Map loadAll(@NonNull Iterable keys) throws Exception {

                        if (keys == null) {
                            return Collections.emptyMap();
                        }
                        Map<String, String> map = new HashMap<>();
                        for (Object key : keys) {
                            map.put((String) key, createExpensiveValue());
                        }
                        return map;
                    }
                });

        // 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回null
        String a = loadingCache.get("a");
        System.out.println(a);

        // 批量查找缓存,如果缓存不存在则生成缓存元素
        Set<String> keys = new HashSet<>();
        keys.add("a");
        keys.add("b");
        Map<String, String> allValues = loadingCache.getAll(keys);
        System.out.println(allValues);
    }

    private static String createExpensiveValue() {
        {
            System.out.println("begin query ..." + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("end query ...");
            return UUID.randomUUID().toString();
        }
    }

一个LoadingCache是Cache附加一个CacheLoader能力之后的缓存实现。

getAll方法中,将会对每个key调用一次CacheLoader.load来生成元素,当批量查询效率更高的时候,你可以自定义loadAll方法实现。

3、手动异步加载

public static void demo() throws ExecutionException, InterruptedException {
    AsyncCache<String,String> asyncCache = Caffeine.newBuilder()
            .maximumSize(100)
            .buildAsync();

    // 添加或者更新一个缓存元素
    asyncCache.put("a",CompletableFuture.completedFuture("a"));

    // 查找一个缓存元素, 没有查找到的时候返回null
    CompletableFuture<String> a = asyncCache.getIfPresent("a");
    System.out.println(a.get());

    // 查找缓存元素,如果不存在,则异步生成
    CompletableFuture<String> completableFuture = asyncCache.get("b", k ->createExpensiveValue("b"));

    System.out.println(completableFuture.get());
    
    // 移除一个缓存元素
    asyncCache.synchronous().invalidate("a");
    System.out.println(asyncCache.getIfPresent("a"));
}

private static String createExpensiveValue(String key) {
    {
        System.out.println("begin query ..." + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("end query ...");
        return UUID.randomUUID().toString();
    }
}

一个AsyncCacheCache的一个变体,AsyncCache提供了在 Executor上生成缓存元素并返回 CompletableFuture的能力。这给出了在当前流行的响应式编程模型中利用缓存的能力。

synchronous()方法给 Cache提供了阻塞直到异步缓存生成完毕的能力。

异步缓存默认的线程池实现是 ForkJoinPool.commonPool() ,你也可以通过覆盖并实现 Caffeine.executor(Executor)方法来自定义你的线程池选择。

4、自动异步加载

public static void demo() throws ExecutionException, InterruptedException {

    AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(10_000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            // 你可以选择: 去异步的封装一段同步操作来生成缓存元素
            //.buildAsync(key -> createExpensiveValue(key));
            // 你也可以选择: 构建一个异步缓存元素操作并返回一个future
            .buildAsync((key, executor) ->createExpensiveValueAsync(key, executor));

    // 查找缓存元素,如果其不存在,将会异步进行生成
    CompletableFuture<String> a = cache.get("a");
    System.out.println(a.get());

    // 批量查找缓存元素,如果其不存在,将会异步进行生成
    Set<String> keys = new HashSet<>();
    keys.add("a");
    keys.add("b");
    CompletableFuture<Map<String, String>> values = cache.getAll(keys);
    System.out.println(values.get());
}

private static String createExpensiveValue(String key) {
    {
        System.out.println("begin query ..." + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("end query ...");
        return UUID.randomUUID().toString();
    }
}

private static CompletableFuture<String> createExpensiveValueAsync(String key, Executor executor) {
    {
        System.out.println("begin query ..." + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
            executor.execute(()-> System.out.println("async create value...."));
        } catch (InterruptedException e) {
        }
        System.out.println("end query ...");
        return CompletableFuture.completedFuture(UUID.randomUUID().toString());
    }
}

一个 AsyncLoadingCache是一个 AsyncCache 加上 AsyncCacheLoader能力的实现。

在需要同步的方式去生成缓存元素的时候,CacheLoader是合适的选择。而在异步生成缓存的场景下, AsyncCacheLoader则是更合适的选择并且它会返回一个 CompletableFuture。

三、驱除策略

Caffeine 提供了三种驱逐策略,分别是基于容量,基于时间和基于引用三种类型;还提供了手动移除方法和监听器。

1、基于容量

// 基于缓存容量大小,缓存中个数进行驱逐
Cache<String, String> cache =
        Caffeine.newBuilder()
                .maximumSize(100)
                .recordStats()
                .build();

// 基于缓存的权重进行驱逐
AsyncCache<String,String> asyncCache = Caffeine.newBuilder()
                .maximumWeight(10)
                .buildAsync();

2、基于时间

// 基于固定时间
Cache<Object, Object> cache =
        Caffeine.newBuilder()
//距离上次访问后一分钟删除
                .expireAfterAccess(Duration.ofMinutes(1))
                .recordStats()
                .build();

Cache<Object, Object> cache =
                Caffeine.newBuilder()
// 距离上次写入一分钟后删除
                        .expireAfterWrite(Duration.ofMinutes(1))
                        .recordStats()
                        .build();
// 基于不同的过期驱逐策略
Cache<String, String> expire =
                Caffeine.newBuilder()
                        .expireAfter(new Expiry<String, String>() {
                            @Override
                            public long expireAfterCreate(@NonNull String key, @NonNull String value, long currentTime) {
                                return LocalDateTime.now().plusMinutes(5).getSecond();
                            }

                            @Override
                            public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
                                return currentDuration;
                            }

                            @Override
                            public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
                                return currentDuration;
                            }
                        })
                        .recordStats()
                        .build();

Caffeine提供了三种方法进行基于时间的驱逐:

expireAfterAccess(long, TimeUnit): 一个值在最近一次访问后,一段时间没访问时被淘汰。

expireAfterWrite(long, TimeUnit): 一个值在初次创建或最近一次更新后,一段时间后被淘汰。

expireAfter(Expiry): 一个值将会在指定的时间后被认定为过期项。

3、基于引用

java对象引用汇总表:

Caffeine学习笔记_System

// 当key和缓存元素都不再存在其他强引用的时候驱逐
LoadingCache<Object, Object> weak = Caffeine.newBuilder()
        .weakKeys()
        .weakValues()
        .build(k ->createExpensiveValue());

// 当进行GC的时候进行驱逐
LoadingCache<Object, Object> soft = Caffeine.newBuilder()
        .softValues()
        .build(k ->createExpensiveValue());

weakKeys:使用弱引用存储key时,当没有其他的强引用时,则会被垃圾回收器回收。

weakValues:使用弱引用存储value时,当没有其他的强引用时,则会被垃圾回收器回收。

softValues:使用软引用存储key时,当没有其他的强引用时,内存不足时会被回收。

4、手动移除

Cache<Object, Object> cache =
                Caffeine.newBuilder()
                        .expireAfterWrite(Duration.ofMinutes(1))
                        .recordStats()
                        .build();
// 单个删除
cache.invalidate("a");
// 批量删除
Set<String> keys = new HashSet<>();
keys.add("a");
keys.add("b");
cache.invalidateAll(keys);

// 失效所有key
cache.invalidateAll();

任何时候都可以手动删除,不用等到驱逐策略生效。

5、移除监听器

Cache<Object, Object> cache =
        Caffeine.newBuilder()
                .expireAfterWrite(Duration.ofMinutes(1))
                .recordStats()
                .evictionListener(new RemovalListener<Object, Object>() {
                    @Override
                    public void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause cause) {
                        System.out.println("element evict cause" + cause.name());
                    }
                })
                .removalListener(new RemovalListener<Object, Object>() {
                    @Override
                    public void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause cause) {
                        System.out.println("element removed cause" + cause.name());
                    }
                }).build();

你可以为你的缓存通过Caffeine.removalListener(RemovalListener)方法定义一个移除监听器在一个元素被移除的时候进行相应的操作。这些操作是使用 Executor异步执行的,其中默认的 Executor 实现是 ForkJoinPool.commonPool()并且可以通过覆盖Caffeine.executor(Executor)方法自定义线程池的实现。

注意:Caffeine.evictionListener(RemovalListener)。这个监听器将在 RemovalCause.wasEvicted()为 true 的时候被触发。

6、驱逐原因汇总

EXPLICIT:如果原因是这个,那么意味着数据被我们手动的remove掉了 REPLACED:就是替换了,也就是put数据的时候旧的数据被覆盖导致的移除 COLLECTED:这个有歧义点,其实就是收集,也就是垃圾回收导致的,一般是用弱引用或者软引用会导致这个情况 EXPIRED:数据过期,无需解释的原因。 SIZE:个数超过限制导致的移除

四、缓存统计

Caffeine通过使用Caffeine.recordStats()方法可以打开数据收集功能,可以帮助优化缓存使用。

// 缓存访问统计
CacheStats stats = cache.stats();
System.out.println("stats.hitCount():"+stats.hitCount());//命中次数
System.out.println("stats.hitRate():"+stats.hitRate());//缓存命中率
System.out.println("stats.missCount():"+stats.missCount());//未命中次数
System.out.println("stats.missRate():"+stats.missRate());//未命中率
System.out.println("stats.loadSuccessCount():"+stats.loadSuccessCount());//加载成功的次数
System.out.println("stats.loadFailureCount():"+stats.loadFailureCount());//加载失败的次数,返回null
System.out.println("stats.loadFailureRate():"+stats.loadFailureRate());//加载失败的百分比
System.out.println("stats.totalLoadTime():"+stats.totalLoadTime());//总加载时间,单位ns
System.out.println("stats.evictionCount():"+stats.evictionCount());//驱逐次数
System.out.println("stats.evictionWeight():"+stats.evictionWeight());//驱逐的weight值总和
System.out.println("stats.requestCount():"+stats.requestCount());//请求次数
System.out.println("stats.averageLoadPenalty():"+stats.averageLoadPenalty());//单次load平均耗时

标签:缓存,stats,Caffeine,笔记,System,学习,println,out
From: https://blog.51cto.com/u_15714439/12267985

相关文章

  • 读书笔记《PPT演讲力》大树模型
    作者把PPT演讲比作一棵大树,树的每一部分对应着PPT演讲的一个技巧。根据这个大树模型,是否有联想到自己过往的演讲经历?演讲是否都达到了大树模型中说的效果?根据这个思维导图,结合自己的经历,试着总结3句关于自己的演讲经历的话,可以是演讲成功总结、演讲没达到的效果的总结、演......
  • 【计算机毕设选题推荐】基于Python的考研学习系统的设计与实现 【附源码+部署+讲解】
    ✍✍计算机毕设编程指导师**⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流!⚡⚡Java、Python、小程序、大数据实战项目集⚡⚡文末获取......
  • spring上 -基于注解配置bean,动态代理,AOP笔记
     用的是jdk8,spring框架里jar包的下载可以自己搜到注解用到的jar包。  60,注解配置Bean快速入门 基本介绍 代码结构: UserDao.javapackagecom.hspedu.spring.component;importorg.springframework.stereotype.Repository;/**使用@Repository标识该......
  • 网络编程学习
    1、什么是网络编程在网络通信协议下,不同计算机上运行的程序,进行的数据传输。应用场景:即时通信、网游对战、金融证券、邮件等等。不管什么场景,都是计算机跟计算机之间通过网络进行数据传输。Java中可以使用java.net包下的技术轻松开发出常见的网络应用程序。2、常见的软件架......
  • 多线程&JUC的学习
    1、什么是线程?进程:进程是程序的基本执行实体。一个软件运行之后就是一个进程。线程:是操作系统能够运行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。简单理解:应用软件中互相独立,可以同时运行的功能。2、多线程的作用?        提高效率。3、多线......
  • 大模型学习攻略,收藏这篇就够了!
    LLMFundamentals基础1.机器学习的数学基础在掌握机器学习之前,理解支撑这些算法的基本数学概念非常重要。线性代数:这是理解许多算法(特别是深度学习算法)的关键。主要概念包括向量、矩阵、行列式、特征值和特征向量、向量空间以及线性变换。微积分:许多机器学习算法涉......
  • bootloader学习笔记-从零开发bootloader(4)
    概要Flash区域划分,从不同的区域启动用户程序,实现覆写代码的功能。Flash区域划分我们的Flash是从0x08000000开始的,具体能用的大小需要查看芯片手册,例如,我的GD32F303RC芯片,flash可用的区域为256KB,内存可用大小为48KB。256KB也就是262144字节的大小,转换成16进制为0x40000,也......
  • CF1672F 做题笔记
    CF1672F1CF1672F2考虑给定\(b\)算它的最小操作次数,我们将\(a_i\)向\(b_i\)连一条边,每个环需要大小减\(1\)次操作次数,所以求这张图的最大简单环划分,显然每个环中不会有相同元素,否则可以分裂成\(2\)个小环更优。F1需要构造使最小次数最大的\(b\),那么就是要最小化最大......
  • 如果你的PyTorch优化器效果欠佳,试试这4种深度学习中的高级优化技术吧
    在深度学习领域,优化器的选择对模型性能至关重要。虽然PyTorch中的标准优化器如SGD、Adam和AdamW被广泛应用,但它们并非在所有情况下都是最优选择。本文将介绍四种高级优化技术,这些技术在某些任务中可能优于传统方法,特别是在面对复杂优化问题时。我们将探讨以下算法:......
  • 机器学习—— 机器学习运维(MLOps)
    机器学习——机器学习运维(MLOps)机器学习运维(MLOps)——提高模型管理和部署效率的必备技能什么是MLOps?为什么MLOps很重要?MLOps示例:构建一个简单的ML流水线MLOps的关键工具总结机器学习运维(MLOps)——高效管理和部署AI模型的工具MLOps的优势MLOps实践的关键工具示例代码......