本地缓存之王Caffeine 保姆级教程(值得珍藏)

1. 简介

在编程领域,缓存是不可或缺的一部分,从处理器到应用层,其应用无处不在。从根本上讲,缓存是利用空间换取时间的一种策略,通过优化数据存储方式,提高后续数据访问速度。

对于Java开发者来说,有很多常用的缓存解决方案,例如EhCache和Memcached等。这些解决方案的核心目标是提高系统吞吐量,减轻数据库等持久层的压力。

根据其部署和应用范围,缓存可以分为本地缓存和分布式缓存两种类型。Caffeine是一种非常优秀的本地缓存解决方案,而Redis则广泛用于分布式缓存场景。

Caffeine是一个基于Java 1.8的高性能本地缓存库,源自Guava的改进。自Spring 5开始,Caffeine已成为默认的缓存实现,取代了原先的Google Guava。官方资料显示,Caffeine的缓存命中率已接近理论最优值。实际上,Caffeine与ConcurrentMap在功能上有许多相似之处,都支持并发操作,且数据的存取时间复杂度为O(1)。然而,二者在数据管理策略上存在显著差异:

  • ConcurrentMap会保留存入的所有数据,除非用户显式地移除;
  • 而Caffeine则根据预设的配置自动剔除“不常用”的数据,确保内存的合理使用。

因此,更恰当的理解是:Cache是一种具备存储和移除策略的Map。

2. 核心特性

  • 高性能:Caffeine采用了多种优化技术,包括基于链表和哈希表的数据结构、优化的内存访问模式以及针对并发访问的优化算法,以减少缓存的内存占用和提高缓存访问速度。这使得它在读写操作上有着卓越的表现。
  • 内存管理:Caffeine实现了自适应的内存管理,能够根据缓存的使用情况动态调整内存分配。它还支持不同的缓存过期策略,有效控制内存使用。
  • 过期策略:Caffeine支持多种缓存过期策略,如基于时间、基于大小、基于引用等,同时也允许用户自定义过期策略。
  • 简洁而强大的API:Caffeine提供了简洁而强大的API,使得缓存的创建和使用变得相对简单。
  • 缓存加载器:Caffeine提供了CacheLoader接口,使得异步加载和刷新缓存项变得更容易。
  • 监听器和事件:可以使用监听器跟踪缓存的变化,对缓存进行事件监听和处理。

3. Caffeine使用

3.1 手动创建

import com.github.benmanes.caffeine.cache.Cache;  
import com.github.benmanes.caffeine.cache.Caffeine;  import java.util.concurrent.TimeUnit;  public class CaffeineManualExample {  // 创建一个指定最大容量的缓存,缓存项在写入后10分钟过期  private static final Cache<String, String> CACHE = Caffeine.newBuilder()  .maximumSize(100)  .expireAfterWrite(10, TimeUnit.MINUTES)  .build();  public static void main(String[] args) {  // 向缓存中添加数据  CACHE.put("key1", "value1");  CACHE.put("key2", "value2");  // 从缓存中获取数据  String value1 = CACHE.getIfPresent("key1");  System.out.println("Value for key1: " + value1);  // 等待一段时间后,缓存项过期,再次尝试获取将返回null  try {  Thread.sleep(TimeUnit.MINUTES.toMillis(15));  } catch (InterruptedException e) {  e.printStackTrace();  }  String value2 = CACHE.getIfPresent("key2");  System.out.println("Value for key2 (after expiration): " + value2); // 输出:null  }  
}

3.2 自动创建

在Spring Boot应用中,你可以使用@Cacheable@CacheEvict注解来自动创建缓存。首先,确保你的Spring Boot项目已经添加了Spring Boot Cache Starter的依赖。

常用注解

  • @Cacheable :表示该方法支持缓存。当调用被注解的方法时,如果对应的键已经存在缓存,则不再执行方法体,而从缓存中直接返回。当方法返回null时,将不进行缓存操作。
  • @CachePut :表示执行该方法后,其值将作为最新结果更新到缓存中,每次都会执行该方法。
  • @CacheEvict :表示执行该方法后,将触发缓存清除操作。
  • @Caching :用于组合前三个注解。

常用注解属性

  • cacheNames/value :缓存组件的名字,即cacheManager中缓存的名称。
  • key :缓存数据时使用的key。默认使用方法参数值,也可以使用SpEL表达式进行编写。
  • keyGenerator :和key二选一使用。
  • cacheManager :指定使用的缓存管理器。
  • condition :在方法执行开始前检查,在符合condition的情况下,进行缓存。
  • unless :在方法执行完成后检查,在符合unless的情况下,不进行缓存。
  • sync :是否使用同步模式。若使用同步模式,在多个线程同时对一个key进行load时,其他线程将被阻塞。

下面是一个简单的例子:

  1. 添加依赖:在pom.xml中添加Spring Boot Cache Starter的依赖
<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-cache</artifactId>  
</dependency>
  1. 配置缓存:在application.propertiesapplication.yml中配置Caffeine作为缓存提供者
spring.cache.type=caffeine  
spring.cache.caffeine.spec=maximumSize=500,expireAfterWrite=60m # 指定缓存最大容量为500,缓存项在写入后60分钟过期。
  1. 使用注解:创建一个服务类,并使用@Cacheable@CacheEvict注解来标记方法
import org.springframework.cache.annotation.Cacheable;  
import org.springframework.cache.annotation.CacheEvict;  
import org.springframework.stereotype.Service;  @Service  
public class ExampleService {  @Cacheable(value = "exampleCache") // 将方法结果缓存到名为"exampleCache"的缓存中。  public String getData() {  // 模拟耗时操作,如数据库查询。这里只是返回一个字符串。  return "data";  }@CacheEvict(value = "exampleCache", key = "#id") // 从名为"exampleCache"的缓存中移除指定键(#id)的缓存项。  public void evictData(String id) { // 该方法目前没有实际功能,只是为了演示如何使用@CacheEvict。 }  
}

3.3 Caffeine的异步获取

Caffeine的异步获取功能允许你在缓存数据加载时执行异步操作,从而减少应用程序的等待时间。你可以使用CacheLoader接口来定义异步加载缓存项的逻辑。当缓存中没有对应的数据时,Caffeine会自动触发CacheLoader的实现类来异步加载数据。一旦数据加载完成,它将被存入缓存并返回给调用者。使用异步获取功能可以解决应用程序在等待数据加载时阻塞的问题,提高整体性能。

使用示例

  1. 定义一个实现CacheLoader接口的类,实现load方法:
import com.github.benmanes.caffeine.cache.CacheLoader;  
import com.github.benmanes.caffeine.cache.LoadingCache;  import java.util.concurrent.CompletableFuture;  
import java.util.concurrent.TimeUnit;  public class AsyncDataLoader implements CacheLoader<String, String> {  @Override  public CompletableFuture<String> load(String key) throws Exception {  // 模拟耗时操作,如数据库查询。这里只是返回一个字符串。  String data = "Async data for key: " + key;  return CompletableFuture.completedFuture(data); // 返回异步加载的结果。  }  
}
  1. 创建一个基于AsyncDataLoaderLoadingCache实例:
import com.github.benmanes.caffeine.cache.Caffeine;  
import com.github.benmanes.caffeine.cache.LoadingCache;  import java.util.concurrent.TimeUnit;  public class CaffeineAsyncExample {  public static void main(String[] args) {  // 创建异步加载缓存的实例,指定缓存的容量和过期时间等配置。  LoadingCache<String, String> cache = Caffeine.newBuilder()  .maximumSize(100) // 缓存最大容量为100个键值对。  .expireAfterWrite(10, TimeUnit.MINUTES) // 缓存项在写入后10分钟过期。  .build(new AsyncDataLoader()); // 使用自定义的AsyncDataLoader作为加载器。  // 从缓存中获取数据,如果缓存中没有数据,则会触发异步加载。  String data = cache.get("key1"); // 返回的数据可能是null,因为此时数据可能正在异步加载中。  System.out.println("Data for key1: " + data); // 输出可能为null,因为数据可能还没有加载完成。  }  
}

在上面的示例中,我们定义了一个AsyncDataLoader类来实现CacheLoader接口。该类中的load方法用于异步加载数据。然后,我们使用Caffeine的LoadingCache构建器创建了一个基于AsyncDataLoader的缓存实例。当从缓存中获取数据时,如果缓存中没有对应的数据,Caffeine会自动调用AsyncDataLoaderload方法进行异步加载。请注意,获取到的数据可能为null,因为此时数据可能正在异步加载中。你可以根据实际需求对异步加载的数据进行处理。

3.4 驱逐策略

Caffeine 的驱逐策略决定了当缓存满了之后,哪些缓存项会被移除。了解驱逐策略对于正确使用缓存和优化缓存性能非常重要。

Caffeine 支持多种驱逐策略,以下是其中一些:

  1. 最近最少使用(LRU):这是最常见的驱逐策略,它将最长时间未被使用的缓存项驱逐出缓存。当缓存满了并且需要添加新的缓存项时,它会移除最长时间未被使用的缓存项。
  2. 先进先出(FIFO):这种策略按照缓存项的插入顺序进行驱逐。当缓存满了并且需要添加新的缓存项时,它会移除最早插入的缓存项。
  3. 基于大小的驱逐:这种策略根据缓存项的大小进行驱逐。当缓存满了并且需要添加新的缓存项时,它会移除最小的缓存项。这对于需要存储大量数据的场景非常有用,例如图片缓存。
  4. 基于时间的驱逐:这种策略根据缓存项的过期时间进行驱逐。当缓存满了并且需要添加新的缓存项时,它会移除最早过期的缓存项。
  5. 基于引用计数的驱逐:这种策略使用引用计数来跟踪缓存项的引用次数。当缓存满了并且需要添加新的缓存项时,它会移除引用计数最低的缓存项。这可以帮助避免热点数据占据整个缓存空间。
  6. 基于权重的驱逐:这种策略根据缓存项的权重进行驱逐。当缓存满了并且需要添加新的缓存项时,它会移除权重最小的缓存项。这可以帮助在缓存中保留更有价值的数据。

在 Caffeine 中,你可以通过 Cache 构造函数的第二个参数来指定驱逐策略。例如,如果你想使用 LRU 驱逐策略,你可以这样做:

Cache<KeyType, ValueType> cache = Caffeine.newBuilder()  .maximumSize(100) // 设置最大容量为100  .evictionPolicy(Eviction.LRU) // 设置驱逐策略为LRU  .build();

3.5 刷新机制

在 Caffeine 中,缓存数据在达到一定条件时会进行刷新,以保持缓存的时效性。下面将对 Caffeine 的刷新机制进行详细讲解。

  1. 缓存项过期

Caffeine 支持设置缓存项的过期时间,当缓存项过期后,Caffeine 会自动将其从缓存中删除。默认情况下,Caffeine 使用 JVM 的当前时间作为过期时间的起始点,即从缓存项创建或最后一次修改时开始计时。当缓存项过期后,Caffeine 会触发一次刷新操作,重新加载缓存项的值。

  1. 懒加载

Caffeine 支持懒加载机制,即只有在访问已过期的缓存项时,才会触发刷新操作。这种机制可以减少不必要的缓存刷新操作,提高缓存的效率。当访问已过期的缓存项时,Caffeine 会异步地执行刷新操作,同时返回缓存项的旧值。一旦刷新操作完成,缓存项的值将被更新,下次访问时将返回新值。

  1. 主动刷新

除了基于过期时间和懒加载的自动刷新外,Caffeine 还支持主动刷新机制。通过调用 Caffeine 实例的 refresh 方法,可以强制刷新指定的缓存项。这种机制适用于在某些情况下需要立即获取最新数据的情况。需要注意的是,主动刷新可能会对缓存性能产生一定的影响,因此应该谨慎使用。

  1. 并发控制

在高并发环境下,多个线程可能同时访问已过期的缓存项,导致多个线程同时执行刷新操作。为了避免这种情况,Caffeine 提供了并发控制机制。通过设置 concurrencyLevel 参数,可以指定每个缓存项允许的最大并发刷新操作数。当超过这个限制时,后续的刷新请求将被阻塞或失败,直到有空闲的刷新槽位。这种机制可以有效地防止过多的线程同时执行刷新操作,提高缓存的性能和稳定性。

下面是一个使用 Caffeine 刷新机制的示例:

首先,定义一个实现了 Refreshable 接口的类,该接口有一个 refresh 方法用于刷新缓存项:

public class Data implements Refreshable<Data> {  private String value;  private long timestamp;  public Data(String value) {  this.value = value;  this.timestamp = System.currentTimeMillis();  }  public String getValue() {  return value;  }  public long getTimestamp() {  return timestamp;  }  @Override  public Data refresh(long ttlMillis) {  // 在这里实现刷新逻辑,例如从数据库或其他数据源获取最新数据  // 并更新 timestamp 和 value。  // 如果 ttlMillis 大于 0,表示设置一个时间限制,超过该时间后缓存项将被驱逐。  return this;  }  
}

然后,在 Caffeine 缓存配置中,使用 refreshAfterWrite 方法指定刷新时间间隔:

Cache<String, Data> cache = Caffeine.newBuilder()  .maximumSize(100) // 设置最大容量为100  .expireAfterWrite(10, TimeUnit.MINUTES) // 设置写入后10分钟过期  .refreshAfterWrite(5, TimeUnit.MINUTES) // 设置写入后5分钟刷新一次  .build(key -> new Data(key)); // 自定义加载函数,将键转换为 Data 对象

最后,在需要使用缓存的地方,从缓存中获取数据,并在必要时刷新缓存项:

String key = "exampleKey";  
Data data = cache.get(key);  
if (data != null && System.currentTimeMillis() - data.getTimestamp() > 5 * 60 * 1000) { // 判断是否超过5分钟未刷新  data = data.refresh(30 * 60 * 1000); // 调用 refresh 方法刷新缓存项,设置30分钟TTL(可选)  cache.put(key, data); // 将刷新后的数据重新放入缓存中(可选)  
}

通过上述示例,你可以使用 Caffeine 的刷新机制来自动更新缓存中的数据。需要注意的是,刷新逻辑的具体实现取决于你的业务需求,可能需要从数据库或其他数据源获取最新数据。

4. 总结

Java Caffeine 是一个高效的缓存库,它通过灵活的配置和多种驱逐策略提供了强大的缓存功能。在 Java 应用程序中,Caffeine 可以帮助我们提高数据访问速度,减轻数据库负载,并改善应用程序的性能。

总的来说,Java Caffeine 是一个功能强大、易于使用的缓存库。通过合理地使用 Caffeine,我们可以提高应用程序的性能和响应速度,并降低对数据库的依赖。在处理大量数据和高并发请求时,Caffeine 可以发挥出其优势,成为 Java 开发者的有力助手。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/650350.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

黑豹程序员-vue3实现剪贴板复制

需求 vue中实现复制文字到剪贴板上 注意 创建ClipboardJS对象时&#xff0c;第一个参数绑定组件 class的名称。此时class为此名称的才能有复制功能。 方法代码 <script setup> // npm install clipboardimport ClipboardJS from clipboard//复制文字到剪贴板const c…

【开源】基于JAVA语言的实验室耗材管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 耗材档案模块2.2 耗材入库模块2.3 耗材出库模块2.4 耗材申请模块2.5 耗材审核模块 三、系统展示四、核心代码4.1 查询耗材品类4.2 查询资产出库清单4.3 资产出库4.4 查询入库单4.5 资产入库 五、免责说明 一、摘要 1.1…

【现代控制系统】从状态方程导出微分方程

从状态方程导出微分方程 2023年6月20日 1. 基本方法 状态空间表达式&#xff1a; x ˙ ( t ) A x ( t ) B u ( t ) y ( t ) C x ( t ) D u ( t ) \begin{aligned} &\dot{ x}(t){ A }{ x }(t){ B }{ u } (t) \\ &{ y }(t){ C } { x }(t){ D } { u }(t) \end{alig…

基于Javaweb开发的二手图书零售系统详细设计【附源码】

基于Javaweb开发的二手图书零售系统详细设计【附源码】 &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各种定制系统…

使用 fdisk 和 mkfs 创建并挂载新硬盘

在Linux系统中&#xff0c;管理磁盘空间是一项关键的任务。有时&#xff0c;我们需要在系统中添加新的硬盘并将其用于特定的用途&#xff0c;比如存储日志文件。本文将介绍如何在Linux系统上使用fdisk和mkfs工具创建、格式化和挂载新硬盘。 1. 确认可用磁盘 首先&#xff0c;…

java的==运算符和equals详解

①chatgpt的解释 在Java中&#xff0c;和equals都是用于比较两个对象的操作符&#xff0c;但它们的行为和用途有所不同。 操作符&#xff1a; 对于基本数据类型&#xff0c;比较的是它们的值是否相等。例如&#xff0c;int a 5; int b 5; System.out.println(a b); // 输出t…

内网穿透natapp使用教程(Linux)

我的使用场景&#xff1a;在家访问学校服务器&#xff0c;由于不在一个局域网&#xff0c;所以需要使用内网穿透&#xff0c;我使用的是natapp。需要在有局域网的时候做好以下步骤。 &#xff08;natapp官网&#xff1a;https://natapp.cn/&#xff09; 1. 下载客户端 &#x…

springboot入门2

学习目标&#xff1a; 了解数据库配置加密方法&#xff0c;数据库连接池&#xff0c;mybatis-paginationInterceptor分页&#xff0c;pagehelper分页常用功能 学习内容&#xff1a; 1、mybatis plus配置加密 1.1、生成加密配置 package sccba.example;import com.baomidou…

模式设计:工厂模式

工厂设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。工厂模式提供了一种创建对象的方式&#xff0c;而无需指定要创建的具体类。工厂模式属于创建型模式&#xff0c;它在创建对象时提供了一种封装机制&#xff0c;将实际创建对象的代码与使用代码分离。 …

盲盒App小程序开发:引领未来购物新潮流

随着科技的不断发展&#xff0c;我们的购物方式也在不断改变。近年来&#xff0c;盲盒购物逐渐成为了一种新型的消费模式&#xff0c;受到了广大消费者的热烈欢迎。为了满足消费者的需求&#xff0c;越来越多的企业开始涉足盲盒App的开发。本文将探讨盲盒App开发的意义、前景以…

使用Docker搭建开发环境:MySQL、Redis、MongoDB和Selenium Grid

Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何支持Docker的平台上。在本篇博客中&#xff0c;我们将详细介绍如何用Docker安装MySQL、Redis、MongoDB和Selenium Grid&#xff0c;并给出相应…

[嵌入式软件][启蒙篇][仿真平台] STM32F103实现IIC控制OLED屏幕

上一篇&#xff1a;[嵌入式软件][启蒙篇][仿真平台] STM32F103实现LED、按键 [嵌入式软件][启蒙篇][仿真平台] STM32F103实现串口输出输入、ADC采集 [嵌入式软件][启蒙篇][仿真平台]STM32F103实现定时器 [嵌入式软件][启蒙篇][仿真平台] STM32F103实现IIC控制OLED屏幕 文章目…

【GitHub项目推荐--国外名校AI教程】【转载】

这个开源项目搜集了 YouTube 上优质的机器学习教程&#xff0c;方向包括机器学习、深度学习、计算机视觉、自然语言处理、无监督学习等等。 开源地址&#xff1a;https://github.com/dair-ai/ML-YouTube-Courses

[260. 只出现一次的数字 III](C语言题解)(位运算)(力扣)

> Problem: [260. 只出现一次的数字 III](260. 只出现一次的数字 III - 力扣&#xff08;LeetCode&#xff09;) # 思路 > 想到数组中只有一个数只出现了一次的解法&#xff1a;**所有数异或&#xff0c;最后答案就是那个只出现一次的数**&#xff0c;该题只需将两个不…

Kubernetes成本优化

云原生可以帮助团队更精细化利用资源&#xff0c;但如果缺乏工具的帮助&#xff0c;很难采取适当的措施优化资源的使用。本文介绍了若干用于可视化Kubernetes资源使用情况的工具&#xff0c;并且可以自定义策略优化资源使用&#xff0c;实现更好的成本优化。原文: Kubernetes C…

新概念英语第二册(42)上

【New words and expressions】生词和短语&#xff08;13&#xff09; musical adj. 精通音乐的 market n. 市场&#xff0c;集市 snake charmer 玩蛇者&#xff08;通常借音乐控制&#xff09; pipe …

《WebKit 技术内幕》学习之十五(5):Web前端的未来

5 Crosswalk项目 Crosswalk项目是由英特尔公司发起的一个开源项目&#xff0c;该项目基于WebKit&#xff08;Blink&#xff09;和Chromium等开源项目打造&#xff0c;其目的是提供一个跨不同操作系统的Web运行环境&#xff0c;包括Android、Tizen、Linux、Windows、MacOS等众多…

c语言-文件的读写操作(上)

文章目录 前言一、文件基础1.1 文件的分类1.2 文件路径和文件名 二、文件的打开和关闭2.1 文件指针2.2 文件的打开和关闭 三、文件顺序读写3.1 fputc()和fgetc()3.2 fputs()和fgets()3.3 fprintf()和fscanf()3.4 fwrite()和fread()3.4 对比一组函数 总结 前言 本篇文章介绍c语…

python使用PaddleOCR实现《命名实体识别项目》OCR(已实现)(ai领域必看,简单易用)

1.简介&#xff1a; PaddleOCR是飞桨&#xff08;PaddlePaddle&#xff09;推出的一个端到端的光学字符识别开源工具集&#xff0c;支持中文、英文、数字以及特殊符号等各种类型的文字检测、识别和词语整体识别。该工具集使用PaddlePaddle深度学习框架技术&#xff0c;提供了多…

Likeshop多商户商城源码系统,支持二开

在电商行业高速发展的当下&#xff0c;拥有一套功能强大、易于操作的开源商城系统至关重要。Likeshop多商户商城系统正是这样一款集H5、小程序、独立APP于一体的开源电商解决方案&#xff0c;助力商家实现智能营销。 一、产品简介 Likeshop多商户商城系统为商家提供了丰富的营…