依赖
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.2.0</version>
</dependency>
Cache的基本api操作
Caffeine.newBuilder.build来构建Caffeine
.maximumSize()设置最大缓存数量
expireAfterWrite(10, TimeUnit.SECONDS) 设置在缓存写后的10分钟过期
expireAfterAccess(10, TimeUnit.SECONDS)设置在缓存被访问后的10分钟过期
put("key", "value");//放入数据
getIfPresent("key-key");//获取数据,存在则返回值,不存在则返回Null
get("key2", k -> "into");//获取缓存,如果不存在则放入数据
invalidate("delete");//删除指定的缓存数据
invalidateAll();//删除所有缓存数据
cleanUp();//清理已经过期或者被标记为无效的缓存项
recordStats()//开启统计
-
CacheStats stats = cache.stats();
-
log.info("命中次数 / 总请求次数:{}",stats.hitRate());
-
log.info("缓存驱逐的次数:{}",stats.evictionCount());
-
log.info("加载均值所花费的平均时间:{}",stats.averageLoadPenalty());
@Test
void test1() {
Cache<String, String> cacheWrite = Caffeine.newBuilder()
.maximumSize(100)//设置缓存的最大条目数
.expireAfterWrite(10, TimeUnit.SECONDS)//设置在缓存写后的10分钟过期
.build();
Cache<String, String> cacheAccess = Caffeine.newBuilder()
.maximumSize(100)//设置缓存的最大条目数
.expireAfterAccess(10, TimeUnit.SECONDS)//设置在缓存被访问后的10分钟过期
.build();cacheWrite.put("key", "value");//放入数据 cacheWrite.getIfPresent("key-key");//获取数据,存在则返回值,不存在则返回Null cacheWrite.get("key2", k -> "into");//获取缓存,如果不存在则放入数据 cacheWr.aite.invalidate("delete");//删除指定的缓存数据 cacheWrite.invalidateAll();//删除所有缓存数据 cacheWrite.cleanUp();//清理已经过期或者被标记为无效的缓存项 //拿出统计信息 Cache<String,String> cache = Caffeine.newBuilder() .maximumSize(10_000) .recordStats()//开启统计 .build(); CacheStats stats = cache.stats(); log.info("命中次数 / 总请求次数:{}",stats.hitRate()); log.info("缓存驱逐的次数:{}",stats.evictionCount()); log.info("加载均值所花费的平均时间:{}",stats.averageLoadPenalty());
}
LoadingCache的自动刷新
refreshAfterWrite(Duration.ofMinutes(1)) 写入一分钟后触发自动刷新
build(刷新方法)
//LoadingCache支持自动加载数据
//可以在构建 LoadingCache 时指定一个 CacheLoader
//当尝试获取一个不存在的缓存项时,LoadingCache 会自动调用 CacheLoader 来加载数据,并将加载的数据存入缓存,然后返回该数据
@Test
void test2() {
// 创建自动加载缓存
LoadingCache<String, String> cache = Caffeine.newBuilder()
.maximumSize(100)
.refreshAfterWrite(Duration.ofMinutes(1))//写入一分钟后触发自动刷新
.build(key -> loadFromDatabase(key)); // 缓存未命中时自动调用此方法
// 使用缓存
String value = cache.get("user101"); // 自动加载
System.out.println(value);
}
private static String loadFromDatabase(String key) {
// 模拟数据库查询
System.out.println("Loading from DB: " + key);
return "data_for_" + key;
}
移除监听器
removalListener()
//移除监听器
@Test
void test3() {
// 创建移除监听器
RemovalListener<String, String> removalListener = (key, value, cause) -> {
System.out.printf("Key %s was removed (%s), value: %s%n", key, cause, value);
};
// 创建 Caffeine 缓存并设置移除监听器
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(2)
.removalListener(removalListener)
.build();
// 向缓存中添加元素
cache.put("key1", "value1");
cache.put("key2", "value2");
cache.put("key3", "value3");
}
写入监听器
//写入监听器
@Test
void test4() {
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(2)
.removalListener((String key, String value, RemovalCause cause) -> {
// 根据 cause 执行不同逻辑
switch (cause) {
case EXPLICIT:
System.out.printf("[手动删除] Key=%s, Value=%s%n", key, value);
break;
case SIZE:
System.out.printf("[容量驱逐] Key=%s (当前值可能已过时)%n", key);
break;
case EXPIRED:
System.out.printf("[过期失效] Key=%s%n", key);
break;
case REPLACED:
System.out.printf("[值被覆盖(写入)] Key=%s (旧值: %s)%n", key, value);
break;
default:
System.out.printf("[其他原因] Key=%s (原因: %s)%n", key, cause);
}
})
.build();
// 测试不同场景
cache.put("k1", "v1");
cache.put("k2", "v2");
cache.put("k3", "v3"); // 触发 SIZE 驱逐 k1
cache.invalidate("k2"); // 触发 EXPLICIT 删除
cache.put("k3", "v3_new"); // 触发 REPLACED
}
定制化缓存清除策略
expireAfter()
//定制化缓存清除策略
void test5() {
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfter(new Expiry<String, String>() {
@Override
public long expireAfterCreate(@NonNull String s, @NonNull String s2, long l) {
log.info("[创建后失效计算 key = {}, value = {}]", s, s2);
// 相当于创建后多少秒就失效了
return TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS);
}
@Override
public long expireAfterUpdate(@NonNull String s, @NonNull String s2, long l, long l1) {
log.info("[更新后失效计算 key = {}, value = {}]", s, s2);
// 更新完多少秒后就失效了
return TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS);
}
@Override
public long expireAfterRead(@NonNull String s, @NonNull String s2, long l, long l1) {
log.info("[读取后失效计算 key = {}, value = {}]", s, s2);
// 读取完多少秒后就失效了
return TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS); // 将2秒转成纳秒
}
}) // 第一步
.build();
cache.put("Bob", "已登录");
cache.put("Lily", "未登录");
cache.put("Wang", "未登录");
cache.put("Lee", "已登录");
log.info("获取缓存数据,Bob= {}", cache.getIfPresent("Bob"));
log.info("获取缓存数据,Lily= {}", cache.getIfPresent("Lily"));
log.info("获取缓存数据,Wang= {}", cache.getIfPresent("Wang"));
log.info("获取缓存数据,Lee= {}", cache.getIfPresent("Lee"));
}
基于权重的缓存清除策略
weigher()
//基于权重的缓存清除策略,总权重大于配置数量时,Caffeine 会优先淘汰那些最近最少被访问的缓存项
@Test
void test6() throws InterruptedException {
Cache<String, String> cache = Caffeine.newBuilder()
.maximumWeight(100)
.weigher((key, value) -> {
log.info("[weigher权重计算器] key = {}, val = {}", key, value);
return 50;
})
.expireAfterAccess(3L, TimeUnit.SECONDS)
.build();
// 放入第一个缓存项,总权重为 50
cache.put("key1", "value1");
System.out.println("放入 key1 后,缓存大小: " + cache.estimatedSize());
// 放入第二个缓存项,总权重为 100
cache.put("key2", "value2");
System.out.println("放入 key2 后,缓存大小: " + cache.estimatedSize());
// 放入第三个缓存项,总权重大于 100,会触发淘汰
cache.put("key3", "value3");
System.out.println("放入 key3 后,缓存大小: " + cache.estimatedSize());
// 等待 3 秒,让缓存项过期
Thread.sleep(3000);
cache.cleanUp();
System.out.println("等待 3 秒后,缓存大小: " + cache.estimatedSize());
}
测试类源码
package com.example.kiratest.test;
import com.github.benmanes.caffeine.cache.*;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import io.micrometer.common.lang.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
@SpringBootTest
@Slf4j
public class CaffeineTest {
@Test
void test1() {
Cache<String, String> cacheWrite = Caffeine.newBuilder()
.maximumSize(100)//设置缓存的最大条目数
.expireAfterWrite(10, TimeUnit.SECONDS)//设置在缓存写后的10分钟过期
.build();
Cache<String, String> cacheAccess = Caffeine.newBuilder()
.maximumSize(100)//设置缓存的最大条目数
.expireAfterAccess(10, TimeUnit.SECONDS)//设置在缓存被访问后的10分钟过期
.build();
cacheWrite.put("key", "value");//放入数据
cacheWrite.getIfPresent("key-key");//获取数据,存在则返回值,不存在则返回Null
cacheWrite.get("key2", k -> "into");//获取缓存,如果不存在则放入数据
cacheWrite.invalidate("delete");//删除指定的缓存数据
cacheWrite.invalidateAll();//删除所有缓存数据
cacheWrite.cleanUp();//清理已经过期或者被标记为无效的缓存项
//拿出统计信息
Cache<String,String> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.recordStats()//开启统计
.build();
CacheStats stats = cache.stats();
log.info("命中次数 / 总请求次数:{}",stats.hitRate());
log.info("缓存驱逐的次数:{}",stats.evictionCount());
log.info("加载均值所花费的平均时间:{}",stats.averageLoadPenalty());
}
//LoadingCache支持自动加载数据
//可以在构建 LoadingCache 时指定一个 CacheLoader
//当尝试获取一个不存在的缓存项时,LoadingCache 会自动调用 CacheLoader 来加载数据,并将加载的数据存入缓存,然后返回该数据
@Test
void test2() {
// 创建自动加载缓存
LoadingCache<String, String> cache = Caffeine.newBuilder()
.maximumSize(100)
.refreshAfterWrite(Duration.ofMinutes(1))//写入一分钟后触发自动刷新
.build(key -> loadFromDatabase(key)); // 缓存未命中时自动调用此方法
// 使用缓存
String value = cache.get("user101"); // 自动加载
System.out.println(value);
}
private static String loadFromDatabase(String key) {
// 模拟数据库查询
System.out.println("Loading from DB: " + key);
return "data_for_" + key;
}
//移除监听器
@Test
void test3() {
// 创建移除监听器
RemovalListener<String, String> removalListener = (key, value, cause) -> {
System.out.printf("Key %s was removed (%s), value: %s%n", key, cause, value);
};
// 创建 Caffeine 缓存并设置移除监听器
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(2)
.removalListener(removalListener)
.build();
// 向缓存中添加元素
cache.put("key1", "value1");
cache.put("key2", "value2");
cache.put("key3", "value3");
}
//写入监听器
@Test
void test4() {
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(2)
.removalListener((String key, String value, RemovalCause cause) -> {
// 根据 cause 执行不同逻辑
switch (cause) {
case EXPLICIT:
System.out.printf("[手动删除] Key=%s, Value=%s%n", key, value);
break;
case SIZE:
System.out.printf("[容量驱逐] Key=%s (当前值可能已过时)%n", key);
break;
case EXPIRED:
System.out.printf("[过期失效] Key=%s%n", key);
break;
case REPLACED:
System.out.printf("[值被覆盖(写入)] Key=%s (旧值: %s)%n", key, value);
break;
default:
System.out.printf("[其他原因] Key=%s (原因: %s)%n", key, cause);
}
})
.build();
// 测试不同场景
cache.put("k1", "v1");
cache.put("k2", "v2");
cache.put("k3", "v3"); // 触发 SIZE 驱逐 k1
cache.invalidate("k2"); // 触发 EXPLICIT 删除
cache.put("k3", "v3_new"); // 触发 REPLACED
}
//定制化缓存清除策略
void test5() {
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfter(new Expiry<String, String>() {
@Override
public long expireAfterCreate(@NonNull String s, @NonNull String s2, long l) {
log.info("[创建后失效计算 key = {}, value = {}]", s, s2);
// 相当于创建后多少秒就失效了
return TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS);
}
@Override
public long expireAfterUpdate(@NonNull String s, @NonNull String s2, long l, long l1) {
log.info("[更新后失效计算 key = {}, value = {}]", s, s2);
// 更新完多少秒后就失效了
return TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS);
}
@Override
public long expireAfterRead(@NonNull String s, @NonNull String s2, long l, long l1) {
log.info("[读取后失效计算 key = {}, value = {}]", s, s2);
// 读取完多少秒后就失效了
return TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS); // 将2秒转成纳秒
}
}) // 第一步
.build();
cache.put("Bob", "已登录");
cache.put("Lily", "未登录");
cache.put("Wang", "未登录");
cache.put("Lee", "已登录");
log.info("获取缓存数据,Bob= {}", cache.getIfPresent("Bob"));
log.info("获取缓存数据,Lily= {}", cache.getIfPresent("Lily"));
log.info("获取缓存数据,Wang= {}", cache.getIfPresent("Wang"));
log.info("获取缓存数据,Lee= {}", cache.getIfPresent("Lee"));
}
//基于权重的缓存清除策略,总权重大于配置数量时,Caffeine 会优先淘汰那些最近最少被访问的缓存项
@Test
void test6() throws InterruptedException {
Cache<String, String> cache = Caffeine.newBuilder()
.maximumWeight(100)
.weigher((key, value) -> {
log.info("[weigher权重计算器] key = {}, val = {}", key, value);
return 50;
})
.expireAfterAccess(3L, TimeUnit.SECONDS)
.build();
// 放入第一个缓存项,总权重为 50
cache.put("key1", "value1");
System.out.println("放入 key1 后,缓存大小: " + cache.estimatedSize());
// 放入第二个缓存项,总权重为 100
cache.put("key2", "value2");
System.out.println("放入 key2 后,缓存大小: " + cache.estimatedSize());
// 放入第三个缓存项,总权重大于 100,会触发淘汰
cache.put("key3", "value3");
System.out.println("放入 key3 后,缓存大小: " + cache.estimatedSize());
// 等待 3 秒,让缓存项过期
Thread.sleep(3000);
cache.cleanUp();
System.out.println("等待 3 秒后,缓存大小: " + cache.estimatedSize());
}
}