广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >java缓存框架Caffeine详解
  • 719
分享到

java缓存框架Caffeine详解

缓存java 2023-10-26 06:10:40 719人浏览 薄情痞子
摘要

缓存在日常开发中启动至关重要的作用,基本是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力,本文主要介绍了本地缓存Caffeine基本配置与基本用法,通过阅读本文,你

缓存在日常开发中启动至关重要的作用,基本是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力,本文主要介绍了本地缓存Caffeine基本配置与基本用法,通过阅读本文,你将了解什么是Caffeine,如何使用Caffeine。

1 Caffeine cache简介

1.1 什么是本地缓存

本地缓存是指将数据存储在设备的硬盘、内存或闪存中,通过空间换时间的策略来减少从远程服务器获取数据的时间和网络开销。

1.2 什么是Caffeine

Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能缓存库。可以说是目前最优秀的本地缓存,性能对比可以参考链接:。
实际上,Caffeine和ConcurrentMap很像——支持并发,并且支持O(1)时间复杂度的数据存取。二者的主要区别在于:

  • ConcurrentMap将存储所有存入的数据,直到你将其手动移除;
  • Caffeine将通过给定的配置,自动移除“不常用”的数据,以保持内存的合理占用。

因此,一种更好的理解方式是:Cache是一种带有存储和移除策略的Map

1.3 什么时候用Caffeine

在选择本地缓存方案时,需要综合考虑数据大小、访问频率、数据一致性、存储安全等多方面因素。如果数据规模不大且对性能要求较高,本地缓存是一种有效的优化策略。例如,对于秒杀系统或者数据量较小的缓存,使用本地缓存可能比远程缓存更加合适。
但是,如果需要处理大规模数据和高并发访问的场景,或者需要实现分布式存储和高可用性,可能需要考虑使用NoSQL数据库,例如Redis等。Nosql数据库具有分布式存储和高可用性等优势,在处理大规模数据和分布式系统时更具优势。

2 Caffeine的基本用法

2.1 添加

Caffeine提供了四种缓存添加策略:手动加载,自动加载,手动异步加载和自动异步加载。

2.1.1 手动加载
Cache<Key, Graph> cache = Caffeine.newBuilder()    .expireAfterWrite(10, TimeUnit.MINUTES)    .maximumSize(10_000)    .build();// 查找一个缓存元素, 没有查找到的时候返回nullGraph graph = cache.getIfPresent(key);// 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回nullgraph = cache.get(key, k -> createExpensiveGraph(key));// 添加或者更新一个缓存元素cache.put(key, graph);// 移除一个缓存元素cache.invalidate(key);

Cache接口提供了显式搜索查找、更新和移除缓存元素的能力。
缓存元素可以通过调用 cache.put(key, value)方法被加入到缓存当中。如果缓存中指定的key已经存在对应的缓存元素的话,那么先前的缓存的元素将会被直接覆盖掉。cache.get(key, k -> value) 采用原子操作的方式将元素插入到缓存中,避免和其他写入竞争。当缓存的元素无法生成或者在生成的过程中抛出异常而导致生成元素失败,**cache.get****可能会返回 null **。

2.1.2 自动加载
LoadinGCache<Key, Graph> cache = Caffeine.newBuilder()    .maximumSize(10_000)    .expireAfterWrite(10, TimeUnit.MINUTES)    .build(key -> createExpensiveGraph(key));// 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回nullGraph graph = cache.get(key);// 批量查找缓存,如果缓存不存在则生成缓存元素Map<Key, Graph> graphs = cache.getAll(keys);

LoadingCache是一种自动加载的缓存。其和普通缓存不同的地方在于,当缓存不存在/缓存已过期时,若调用get()方法,则会自动调用CacheLoader.load()方法生成缓存元素。在默认情况下,调用getAll方法,将会对每个将会对每个不存在对应缓存的key调用一次 CacheLoader.load 来生成缓存元素,可以通过重写CacheLoader.loadAll 方法来提升缓存效率。

2.1.3 手动异步加载
AsyncCache<Key, Graph> cache = Caffeine.newBuilder()    .expireAfterWrite(10, TimeUnit.MINUTES)    .maximumSize(10_000)    .buildAsync();// 查找一个缓存元素, 没有查找到的时候返回nullCompletableFuture<Graph> graph = cache.getIfPresent(key);// 查找缓存元素,如果不存在,则异步生成graph = cache.get(key, k -> createExpensiveGraph(key));// 添加或者更新一个缓存元素cache.put(key, graph);// 移除一个缓存元素cache.synchronous().invalidate(key);

AsyncCache是Cache的一个变体,其响应结果均为CompletableFuture,通过这种方式,AsyncCache对异步编程模式进行了适配。默认情况下,缓存计算使用ForkJoinPool.commonPool()作为线程池,如果想要指定线程池,则可以覆盖并实现Caffeine.executor(Executor)方法。
synchronous()提供了阻塞直到异步缓存生成完毕的能力,它将以Cache进行返回。
多线程情况下,当两个线程同时调用get(key, k -> value),则会返回同一个CompletableFuture对象。由于返回结果本身不进行阻塞,可以根据业务设计自行选择阻塞等待或者非阻塞。

2.1.4 自动异步加载
AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()    .maximumSize(10_000)    .expireAfterWrite(10, TimeUnit.MINUTES)    // 你可以选择: 去异步的封装一段同步操作来生成缓存元素    .buildAsync(key -> createExpensiveGraph(key));    // 你也可以选择: 构建一个异步缓存元素操作并返回一个future    .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));// 查找缓存元素,如果其不存在,将会异步进行生成CompletableFuture<Graph> graph = cache.get(key);// 批量查找缓存元素,如果其不存在,将会异步进行生成CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);

显然这是AsyncCacheAsyncCacheLoader的功能组合。AsyncLoadingCache支持以异步的方式,对缓存进行自动加载。通过 getAll可以达到批量查找缓存的目的。 默认情况下,在getAll 方法中,将会对每个不存在对应缓存的key调用一次AsyncCacheLoader.asyncLoad 来异步生成缓存元素。 在批量检索比单个查找更有效率的场景下,你可以覆盖并开发AsyncCacheLoader.asyncLoadAll方法来使你的缓存更有效率。

2.2 缓存淘汰

Caffeine 提供了三种缓存淘汰策略,分别是基于容量,基于时间和基于引用三种类型。

2.2.1 基于容量
// 基于缓存内的元素个数进行驱逐LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .maximumSize(10_000)    .build(key -> createExpensiveGraph(key));// 基于缓存内元素权重进行驱逐LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .maximumWeight(10_000)    .weigher((Key key, Graph graph) -> graph.vertices().size())    .build(key -> createExpensiveGraph(key));

如果你的缓存容量不希望超过某个特定的大小,使用Caffeine.maximumSize(long)。缓存将会尝试通过基于Window TinyLfu算法来驱逐掉不会再被使用到的元素。
另一种情况,你的缓存中的元素可能存在不同的“权重”,例如,你的缓存中的元素可能有不同的内存占用,你也许需要借助Caffeine.weigher(Weigher)方法来界定每个元素的权重并通过 Caffeine.maximumWeight(long)方法来界定缓存中元素的总权重来实现上述的场景。除了“最大容量”所需要的注意事项,在基于权重驱逐的策略下,一个缓存元素的权重计算是在其创建和更新时,此后其权重值都是静态存在的,在两个元素之间进行权重的比较的时候,并不会根据进行相对权重的比较。

2.2.2 基于时间
// 基于固定的过期时间驱逐策略LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .expireAfterAccess(5, TimeUnit.MINUTES)    .build(key -> createExpensiveGraph(key));LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .expireAfterWrite(10, TimeUnit.MINUTES)    .build(key -> createExpensiveGraph(key));// 基于不同的过期驱逐策略LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .expireAfter(new Expiry<Key, Graph>() {      public long expireAfterCreate(Key key, Graph graph, long currentTime) {        // Use wall clock time, rather than nanotime, if from an external resource        long seconds = graph.creationDate().plusHours(5)            .minus(System.currentTimeMillis(), MILLIS)            .toEpochSecond();        return TimeUnit.SECONDS.toNanos(seconds);      }      public long expireAfterUpdate(Key key, Graph graph,           long currentTime, long currentDuration) {        return currentDuration;      }      public long expireAfterRead(Key key, Graph graph,          long currentTime, long currentDuration) {        return currentDuration;      }    })    .build(key -> createExpensiveGraph(key));

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

  • expireAfterAccess(long, TimeUnit): 一个元素在上一次读写操作后一段时间之后,在指定的时间后没有被再次访问将会被认定为过期项。在当被缓存的元素时被绑定在一个session上时,当session因为不活跃而使元素过期的情况下,这是理想的选择。
  • expireAfterWrite(long, TimeUnit): 一个元素将会在其创建或者最近一次被更新之后的一段时间后被认定为过期项。在对被缓存的元素的时效性存在要求的场景下,这是理想的选择。
  • expireAfter(Expiry): 一个元素将会在指定的时间后被认定为过期项。当被缓存的元素过期时间收到外部资源影响的时候,这是理想的选择。

在写操作,和偶尔的读操作中将会进行周期性的过期事件的执行。过期事件的调度和触发将会在O(1)的时间复杂度内完成。
为了使过期更有效率,可以通过在你的Cache构造器中通过Scheduler接口和Caffeine.scheduler(Scheduler) 方法去指定一个调度线程代替在缓存活动中去对过期事件进行调度。使用Java 9以上版本的用户可以选择Scheduler.systemScheduler()利用系统范围内的调度线程。
测试基于时间的驱逐策略的时候,不需要坐在板凳上等待现实时钟的转动。使用Ticker接口和 Caffeine.ticker(Ticker)方法在你的Cache构造器中去指定一个时间源可以避免苦苦等待时钟转动的麻烦。Guava的测试库也提供了FakeTicker去达到同样的目的。

2.2.3 基于引用
// 当key和缓存元素都不再存在其他强引用的时候驱逐LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .weakKeys()    .weakValues()    .build(key -> createExpensiveGraph(key));// 当进行GC的时候进行驱逐LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .softValues()    .build(key -> createExpensiveGraph(key));

Caffeine 允许你配置你的缓存去让GC去帮助清理缓存当中的元素,其中key支持弱引用,而value则支持弱引用和软引用。记住 AsyncCache不支持软引用和弱引用。
Caffeine.weakKeys() 在保存key的时候将会进行弱引用。这允许在GC的过程中,当key没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等而不是对象相等 equals()去进行key之间的比较。
Caffeine.weakValues()在保存value的时候将会使用弱引用。这允许在GC的过程中,当value没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等而不是对象相等 equals()去进行value之间的比较。
Caffeine.softValues()在保存value的时候将会使用软引用。为了相应内存的需要,在GC过程中被软引用的对象将会被通过LRU算法回收。由于使用软引用可能会影响整体性能,我们还是建议通过使用基于缓存容量的驱逐策略代替软引用的使用。同样的,使用 softValues() 将会通过引用相等而不是对象相等 equals()去进行value之间的比较。

2.3 移除

术语:

  • 驱逐 缓存元素因为策略被移除
  • 失效 缓存元素被手动移除
  • 移除 由于驱逐或者失效而最终导致的结果
2.3.1 显式移除

在任何时候,你都可以手动去让某个缓存元素失效而不是只能等待其因为策略而被驱逐。

// 失效keycache.invalidate(key)// 批量失效keycache.invalidateAll(keys)// 失效所有的keycache.invalidateAll()
2.3.2 移除监听器
Cache<Key, Graph> graphs = Caffeine.newBuilder()    .evictionListener((Key key, Graph graph, RemovalCause cause) ->        System.out.printf("Key %s was evicted (%s)%n", key, cause))    .removalListener((Key key, Graph graph, RemovalCause cause) ->        System.out.printf("Key %s was removed (%s)%n", key, cause))    .build();

你可以为你的缓存通过Caffeine.removalListener(RemovalListener)方法定义一个移除监听器在一个元素被移除的时候进行相应的操作。这些操作是使用 Executor 异步执行的,其中默认的 Executor 实现是 ForkJoinPool.commonPool() 并且可以通过覆盖Caffeine.executor(Executor)方法自定义线程池的实现。
当移除之后的自定义操作必须要同步执行的时候,你需要使用Caffeine.evictionListener(RemovalListener)。这个监听器将在 RemovalCause.wasEvicted() 为 true 的时候被触发。为了移除操作能够明确生效, Cache.asMap() 提供了方法来执行原子操作。

2.4 刷新

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .maximumSize(10_000)    .refreshAfterWrite(1, TimeUnit.MINUTES)    .build(key -> createExpensiveGraph(key));

刷新和驱逐并不相同。可以通过LoadingCache.refresh(K)方法,异步为key对应的缓存元素刷新一个新的值。与驱逐不同的是,在刷新的时候如果查询缓存元素,其旧值将仍被返回,直到该元素的刷新完毕后结束后才会返回刷新后的新值。
expireAfterWrite相反,refreshAfterWrite 将会使在写操作之后的一段时间后允许key对应的缓存元素进行刷新,但是只有在这个key被真正查询到的时候才会正式进行刷新操作。打个比方,你可以在同一个缓存中同时用到 refreshAfterWriteexpireAfterWrite ,这样缓存元素的在被允许刷新的时候不会直接刷新使得过期时间被盲目重置。当一个元素在其被允许刷新但是没有被主动查询的时候,这个元素也会被视为过期。
一个CacheLoader可以通过覆盖重写 CacheLoader.reload(K, V) 方法使得在刷新中可以将旧值也参与到更新的过程中去,这也使得刷新操作显得更加智能。
更新操作将会异步执行在一个Executor上。默认的线程池实现是ForkJoinPool.commonPool(),可以通过覆盖Caffeine.executor(Executor)方法自定义线程池的实现。

3 SpringBoot集成Caffeine

3.1 引入依赖 Maven

<dependency>    <groupId>com.GitHub.ben-manes.caffeinegroupId>    <artifactId>caffeineartifactId>    <version>3.0.5version>dependency>

3.2 配置缓存配置类

@Configurationpublic class CacheConfig {    @Bean    public Cache<String, Object> caffeineCache() {        return Caffeine.newBuilder()                // 设置最后一次写入或访问后经过固定时间过期                .expireAfterWrite(60, TimeUnit.SECONDS)                // 初始的缓存空间大小                .initialCapacity(100)                // 缓存的最大条数                .maximumSize(1000)                .build();    }}
  • initialCapacity: 初始的缓存空间大小。
  • maximumSize: 缓存的最大数量。
  • maximumWeight: 缓存的最大权重。
  • expireAfterAccess: 最后一次读或写操作后经过指定时间过期。
  • expireAfterWrite: 最后一次写操作后经过指定时间过期。
  • refreshAfterWrite: 创建缓存或者最近一次更新缓存后经过指定时间间隔,刷新缓存。
  • weakKeys: 打开key的弱引用。弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
  • weakValues:打开value的弱引用。
  • softValues:打开value的软引用。软引用: 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
  • recordStats:开发统计功能。

注意:

  • weakValues 和 softValues 不可以同时使用。
  • maximumSize 和 maximumWeight 不可以同时使用。
  • expireAfterWrite 和 expireAfterAccess 同事存在时,以 expireAfterWrite 为准。

3.3 使用

@Autowiredprivate Cache<String, Object> caffeineCache;public User getUser(String userId) {        User result = (User) caffeineCache.getIfPresent(userId);        if (result != null) {            return result;        }        // 如果缓存中没有, 从库中查找        User user = userService                .getOne(new LambdaQueryWrapper<User>().eq(User::getUserId, userId));        // 如果用户信息不为空,则加入缓存        if (user != null) {            caffeineCache.put(userId, user);            return user;        }        return null;    }

参考文档:
https://github.com/ben-manes/caffeine/wiki/Benchmarks-zh-CN
https://ghh3809.github.io/2021/05/31/caffeine/

来源地址:https://blog.csdn.net/asxyxxx/article/details/129663791

--结束END--

本文标题: java缓存框架Caffeine详解

本文链接: https://www.lsjlt.com/news/452039.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • java缓存框架Caffeine详解
    缓存在日常开发中启动至关重要的作用,基本是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力,本文主要介绍了本地缓存Caffeine基本配置与基本用法,通过阅读本文,你...
    99+
    2023-10-26
    缓存 java
  • Java高性能本地缓存框架Caffeine的实现
    目录一、序言二、缓存简介(一)缓存对比(二)本地缓存三、SpringCache(一)需求分析(二)序列化(三)集成四、小结一、序言 Caffeine是一个进程内部缓存框架,使用了Ja...
    99+
    2022-11-13
  • Java进程内缓存框架EhCache详解
    目录一:目录二: 简介2.1、基本介绍2.2、主要的特性2.3、 集成2.4、 ehcache 和 redis 比较三:事例3.1、在pom.xml中引入依赖3.2、在src/mai...
    99+
    2022-11-12
  • java若依框架集成redis缓存详解
    目录1、添加依赖2、修改配置3、增加配置4、增加工具类总结1、添加依赖 ruoyi-common\pom.xml模块添加整合依赖 <!-- springb...
    99+
    2022-11-12
  • SpringBoot 缓存 Caffeine使用解析
    目录Redis和Caffeine的区别相同点不同点联系Spring Boot 缓存 Caffeine使用1.需要添加的依赖2.配置3.使用Caffeine缓存Caffeine其他常用...
    99+
    2022-11-12
  • 详解高性能缓存Caffeine原理及实战
    目录一、简介二、Caffeine 原理2.1、淘汰算法2.1.1、常见算法2.1.2、W-TinyLFU 算法2.2、高性能读写2.2.1、读缓冲2.2.2、写缓冲三、Caffein...
    99+
    2022-11-12
  • 轻松了解java中Caffeine高性能缓存库
    目录轻松lCaffeine1、依赖2、写入缓存 2.1、手动写入2.2、同步加载2.3、异步加载3、缓存值的清理3.1、基于大小的清理3.2、基于时间的清理 3....
    99+
    2022-11-12
  • SpringBoot集成本地缓存性能之王Caffeine示例详解
    目录引言Spring Cache 是什么集成 Caffeine核心原理引言 使用缓存的目的就是提高性能,今天码哥带大家实践运用 spring-boot-starter-ca...
    99+
    2022-11-13
  • Java框架---Spring详解
    目录一 技术发展二 框架设计Spring Framework 6大模块三 Spring AOP详解AOP两种方式四 Spring Bean核心原理Bean的加载过程五 Spring ...
    99+
    2022-11-12
  • GoFrame框架缓存查询结果的示例详解
    目录查询缓存相关方法:缓存对象缓存适配(Redis缓存)使用示例数据表结构示例代码小技巧运行结果分析总结后续几篇文章再接再厉,仍然为大家介绍GoFrame框架缓存相关的知识点,以及自...
    99+
    2022-11-13
  • Android边播放边缓存视频框架AndroidVideoCache详解
    目录一、背景二、PlayerBase三、AndroidVideoCache3.1 基本原理3.2 基本使用3.3 源码分析一、背景 现在的移动应用,视频是一个非常重要的组成部分,好像...
    99+
    2022-11-13
  • Java中常用的缓存框架有哪些
    这篇文章主要讲解了“Java中常用的缓存框架有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java中常用的缓存框架有哪些”吧! 0x01:EhcacheEhcach...
    99+
    2023-06-16
  • Java关于MyBatis缓存详解
    目录什么是 MyBatis 缓存MyBatis 缓存分类1.⼀级缓存:SqlSession级别,默认开启,并且不能关闭.(默认开启)2.二级缓存:Mapper 级别,默...
    99+
    2022-11-12
  • Java集合框架之Map详解
    目录1、Map的实现2、HashMap 和 Hashtable 的区别3、介绍下对象的 hashCode()和equals(),使用场景4、HashMap和TreeMap应该怎么选择...
    99+
    2022-11-13
  • Java之JSF框架案例详解
    这是一个分为两部分的系列,其中我介绍了JSF 2及其如何适合Java EE生态系统。 在第1部分中,我将介绍JavaServer Pages(JSF)背后的基本思想 ,在第2部分中,...
    99+
    2022-11-12
  • Java WebService开源框架CXF详解
    目录CXF简介支持多种标准CXF入门案例服务端的实现客户端的实现CXF+Spring整合发布SOAP模式的服务服务端的实现客户端的实现CXF发布REST模式的服务CXF+Spring...
    99+
    2022-11-12
  • Java Executor 框架的实例详解
    Java Executor 框架的实例详解大多数并发都是通过任务执行的方式来实现的。一般有两种方式执行任务:串行和并行。class SingleThreadWebServer { public static void main(String...
    99+
    2023-05-31
    java executor ava
  • Java WebService开源框架CXF详解
    Apache CXF是一个开源的Web服务框架,它提供了创建和部署Web服务的工具和库。它是基于Java的,并且遵循Java的标准规...
    99+
    2023-08-08
    Java
  • spring框架cacheAnnotation缓存注释声明解析
    目录1.基于注释声明缓存1.1@EnableCaching1.2@Cacheable1.2.1默认key生成规则1.2.2声明自定义key 生成1.2.3默认的cache resol...
    99+
    2022-11-12
  • 如何理解Android图片缓存框架Glide
    本篇文章给大家分享的是有关如何理解Android图片缓存框架Glide,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Android图片缓存框架GlideGlide是Google...
    99+
    2023-06-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作