赞
踩
点击下载《本地缓存之王Caffeine 保姆级教程(值得珍藏)》
在编程领域,缓存是不可或缺的一部分,从处理器到应用层,其应用无处不在。从根本上讲,缓存是利用空间换取时间的一种策略,通过优化数据存储方式,提高后续数据访问速度。
对于Java开发者来说,有很多常用的缓存解决方案,例如EhCache和Memcached等。这些解决方案的核心目标是提高系统吞吐量,减轻数据库等持久层的压力。
根据其部署和应用范围,缓存可以分为本地缓存和分布式缓存两种类型。Caffeine是一种非常优秀的本地缓存解决方案,而Redis则广泛用于分布式缓存场景。
Caffeine是一个基于Java 1.8的高性能本地缓存库,源自Guava的改进。自Spring 5开始,Caffeine已成为默认的缓存实现,取代了原先的Google Guava。官方资料显示,Caffeine的缓存命中率已接近理论最优值。实际上,Caffeine与ConcurrentMap在功能上有许多相似之处,都支持并发操作,且数据的存取时间复杂度为O(1)。然而,二者在数据管理策略上存在显著差异:
因此,更恰当的理解是:Cache是一种具备存储和移除策略的Map。
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
}
}
在Spring Boot应用中,你可以使用@Cacheable
和@CacheEvict
注解来自动创建缓存。首先,确保你的Spring Boot项目已经添加了Spring Boot Cache Starter的依赖。
常用注解
常用注解属性
下面是一个简单的例子:
pom.xml
中添加Spring Boot Cache Starter的依赖<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
application.properties
或application.yml
中配置Caffeine作为缓存提供者spring.cache.type=caffeine
spring.cache.caffeine.spec=maximumSize=500,expireAfterWrite=60m # 指定缓存最大容量为500,缓存项在写入后60分钟过期。
@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。
}
}
Caffeine的异步获取功能允许你在缓存数据加载时执行异步操作,从而减少应用程序的等待时间。你可以使用CacheLoader
接口来定义异步加载缓存项的逻辑。当缓存中没有对应的数据时,Caffeine会自动触发CacheLoader
的实现类来异步加载数据。一旦数据加载完成,它将被存入缓存并返回给调用者。使用异步获取功能可以解决应用程序在等待数据加载时阻塞的问题,提高整体性能。
使用示例:
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); // 返回异步加载的结果。
}
}
AsyncDataLoader
的LoadingCache
实例: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会自动调用AsyncDataLoader
的load
方法进行异步加载。请注意,获取到的数据可能为null,因为此时数据可能正在异步加载中。你可以根据实际需求对异步加载的数据进行处理。
Caffeine 的驱逐策略决定了当缓存满了之后,哪些缓存项会被移除。了解驱逐策略对于正确使用缓存和优化缓存性能非常重要。
Caffeine 支持多种驱逐策略,以下是其中一些:
在 Caffeine 中,你可以通过 Cache
构造函数的第二个参数来指定驱逐策略。例如,如果你想使用 LRU 驱逐策略,你可以这样做:
Cache<KeyType, ValueType> cache = Caffeine.newBuilder()
.maximumSize(100) // 设置最大容量为100
.evictionPolicy(Eviction.LRU) // 设置驱逐策略为LRU
.build();
在 Caffeine 中,缓存数据在达到一定条件时会进行刷新,以保持缓存的时效性。下面将对 Caffeine 的刷新机制进行详细讲解。
Caffeine 支持设置缓存项的过期时间,当缓存项过期后,Caffeine 会自动将其从缓存中删除。默认情况下,Caffeine 使用 JVM 的当前时间作为过期时间的起始点,即从缓存项创建或最后一次修改时开始计时。当缓存项过期后,Caffeine 会触发一次刷新操作,重新加载缓存项的值。
Caffeine 支持懒加载机制,即只有在访问已过期的缓存项时,才会触发刷新操作。这种机制可以减少不必要的缓存刷新操作,提高缓存的效率。当访问已过期的缓存项时,Caffeine 会异步地执行刷新操作,同时返回缓存项的旧值。一旦刷新操作完成,缓存项的值将被更新,下次访问时将返回新值。
除了基于过期时间和懒加载的自动刷新外,Caffeine 还支持主动刷新机制。通过调用 Caffeine 实例的 refresh
方法,可以强制刷新指定的缓存项。这种机制适用于在某些情况下需要立即获取最新数据的情况。需要注意的是,主动刷新可能会对缓存性能产生一定的影响,因此应该谨慎使用。
在高并发环境下,多个线程可能同时访问已过期的缓存项,导致多个线程同时执行刷新操作。为了避免这种情况,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 的刷新机制来自动更新缓存中的数据。需要注意的是,刷新逻辑的具体实现取决于你的业务需求,可能需要从数据库或其他数据源获取最新数据。
Java Caffeine 是一个高效的缓存库,它通过灵活的配置和多种驱逐策略提供了强大的缓存功能。在 Java 应用程序中,Caffeine 可以帮助我们提高数据访问速度,减轻数据库负载,并改善应用程序的性能。
总的来说,Java Caffeine 是一个功能强大、易于使用的缓存库。通过合理地使用 Caffeine,我们可以提高应用程序的性能和响应速度,并降低对数据库的依赖。在处理大量数据和高并发请求时,Caffeine 可以发挥出其优势,成为 Java 开发者的有力助手。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。