Caffeine快速入门
依赖
<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");//获取数据,存在则返回值,不存在则返回NullcacheWrite.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 驱逐 k1cache.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>() {@Overridepublic long expireAfterCreate(@NonNull String s, @NonNull String s2, long l) {log.info("[创建后失效计算 key = {}, value = {}]", s, s2);// 相当于创建后多少秒就失效了return TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS);}@Overridepublic 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);}@Overridepublic 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();// 放入第一个缓存项,总权重为 50cache.put("key1", "value1");System.out.println("放入 key1 后,缓存大小: " + cache.estimatedSize());// 放入第二个缓存项,总权重为 100cache.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 {@Testvoid 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");//获取数据,存在则返回值,不存在则返回NullcacheWrite.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 来加载数据,并将加载的数据存入缓存,然后返回该数据@Testvoid 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;}//移除监听器@Testvoid 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");}//写入监听器@Testvoid 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 驱逐 k1cache.invalidate("k2"); // 触发 EXPLICIT 删除cache.put("k3", "v3_new"); // 触发 REPLACED}//定制化缓存清除策略void test5() {Cache<String, String> cache = Caffeine.newBuilder().maximumSize(100).expireAfter(new Expiry<String, String>() {@Overridepublic long expireAfterCreate(@NonNull String s, @NonNull String s2, long l) {log.info("[创建后失效计算 key = {}, value = {}]", s, s2);// 相当于创建后多少秒就失效了return TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS);}@Overridepublic 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);}@Overridepublic 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 会优先淘汰那些最近最少被访问的缓存项@Testvoid 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();// 放入第一个缓存项,总权重为 50cache.put("key1", "value1");System.out.println("放入 key1 后,缓存大小: " + cache.estimatedSize());// 放入第二个缓存项,总权重为 100cache.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());}}