前言
Caffeine是使用Java8对Guava缓存的重写版本,在Spring Boot 2.0中取而代之,基于LRU算法实现,支持多种缓存过期策略。
以下摘抄于https://github.com/ben-manes/caffeine/wiki/Benchmarks-zh-CN
基准测试通过使用Java microbenchmark harness 来提供准确的分析结果。这些缓存将被如下配置,
- Caffeine 和 ConcurrentLinkedHashMap根据CPU的数量调整其内部大小。
- Guava 并发度被配置为
64
(默认情况下为4
来减少内存开销)。请注意Guava将会#2063 解决性能问题,但已经被积压多年(提升25倍以上!)。- Ehcache v2 内部被硬编码为100段, 而 v3 版本没有进行分段
- Infinispan “old” 是一个类似Guava的缓存,并且并发度被配置为
64
- Infinispan "new"是使用无锁deque(默认版本为 v7.2+)重写的
本地缓存对比
特性 | Guava | Caffeine |
---|---|---|
自动加载实体到缓存中 | ✔️ | ✔️ |
自动刷新 | ✔️ | ✔️ |
过期或被删除的机制 | ✔️ | ✔️ |
自动回收 | ✔️ | ✔️ |
统计累计访问缓存 | ✔️ | ✔️ |
异步 | ❌ | ✔️ |
写入外界资源 | ❌ | ✔️ |
算法 | S-LRU 分段的最近最少未使用算法 | W-TinyLFU 高命中率、低内存占用 |
持久化 | ❌ | ❌ |
使用指南
pom引入
<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId>
</dependency>
配置
-
maximumSize:
-
refreshAfterWrite
-
expireAfterWrite
-
expireAfterAccess
-
maximumWeight
方法
-
Get
如果Guava 的load()返回时null,那么get()会抛出异常ExecutionException,而Caffeine就不会,会返回null
-
getAll
获取多个key的value,返回一个map
-
load
刷新
-
getUnchecked(): guava才有,Caffeine没有,所以Caffeine需要做好控判断
-
Caffeine.newBuilder().build()创建LoadingCache
-
invalidate
-
cleanUp
缓存填充方法
-
手动填充
# 0. 构建LoadingCache Cache<String, DataObject> cache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).maximumSize(280).build();String key = "test"; # 1. 缓存中不存指定的值,则方法将返回 null DataObject dataObject = cache.getIfPresent(key); cache.put(key, "20220510 Demo演示"); # 2. return "20220510 Demo演示" dataObject = cache.getIfPresent(key);# 3. 如果key的值不存在,则返回“空时mock一个值” dataObject = cache.get(key, k -> "空时mock一个值"); # 4. 手动失效 cache.invalidate(key); # 返回null dataObject = cache.getIfPresent(key);
-
同步加载
LoadingCache<String, DataObject> cache = Caffeine.newBuilder().maximumSize(100).expireAfterWrite(1, TimeUnit.MINUTES).build(k -> "Data for " + k);
-
异步加载
AsyncLoadingCache<String, DataObject> cache = Caffeine.newBuilder().maximumSize(100).expireAfterWrite(1, TimeUnit.MINUTES).buildAsync(k -> "Data for " + k);String key = "test-async";cache.get(key).thenAccept(dataObject -> {assertNotNull(dataObject);assertEquals("Data for " + key, dataObject.getData()); });cache.getAll(Arrays.asList("test-async", "test-async2", "test-async3")).thenAccept(dataObjectMap -> assertEquals(3, 3));
注解的实现方式
Springboot 配置
spring:
# 配置缓存,初始缓存容量为10,最大容量为200,过期时间(这里配置写入后过期时间为3秒)cache:type: caffeinecaffeine:spec: initialCapacity=10,maximumSize=200,expireAfterWrite=3s
@Cacheable
// key 是指传入时的参数@Cacheable(value="users", key="#id")public Integer find(Integer id) {return id;}
// 表示第一个参数
@Cacheable(value="users", key="#p0")public Long find(Long id) {return id;}
// 表示User中的id值@Cacheable(value="users", key="#user.id")public User find(User user) {return user;}// 表示第一个参数里的id属性值@Cacheable(value="users", key="#p0.id")public User find(User user) {return user;}// 表示第一个参数里的id属性值@Cacheable(value="users", key="#root.getXXX(#user.id)")public User find(User user) {return user;}public String getXXX(String id){// 获取上文问的traceid 方法// ...return 上下文的traceId;
}// 根据条件判断是否缓存@Cacheable(value="users", key="#user.id", condition="#user.id%2==0"/*,unless="#result==null"*/)public User find(User user) {return user;}
注意事项
-
SpringBoot启动类开启@EnableCaching
-
A class 调用 B class 的@Cacheable的方法,一定是public方法
-
A class 调用 B class 的@Cacheable的方法,B 一定是@Component、@Service等
-
B class 的@Cacheable的方法xx1调用@Cacheable的方法xx2,B缓存不起作用。解决方案:Spring上下文手动获取Bean实例调用xx2,或者
AopContext.currentProxy())
调用xx2,不过ApoContenxt这个,需要在启动累开启@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
-
开启@EnableCache,注意区分Redis的CacheManager,在注解通过指定@Cacheable(cacheManager=“caffeineCacheManager”,…)