1. Caffeine的基础使用
借鉴了https://www.jianshu.com/p/9ee291147617
1.1 简介
Caffeine是基于Java 8的高性能,接近最佳的缓存工具库。Caffeine使用Google Guava启发的API提供内存缓存。所以它的使用成本较低,跟Guava的API大致一致。
它主要有以下几个功能:
-
自动将条目自动加载到缓存中,可以选择同步或异步加载
-
基于频率和新近度超过最大值时基于大小的逐出
-
自上次访问或上次写入以来测得的基于时间的条目到期
-
发生第一个陈旧的条目请求时,异步刷新
-
键自动包装在弱引用中
-
值自动包装在弱引用或软引用中
-
逐出(或以其他方式删除)条目的通知
-
写入通知
-
缓存访问统计信息的
1.2 常见API
Cache分为LoadingCache(同步缓存),AsyncLoadingCache(异步缓存)
- pom依赖
<dependency>
<groupId>com.github.benmanes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.8.5</version>
</dependency>
1.2.1 人工加载策略
Cache<Object, Object> cache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)
.expireAfterAccess(1, TimeUnit.SECONDS)
.maximumSize(10)//最大条数
.build();//定义cache
User user1=(User) cache.get(id, v-> userDao.getOne(id));//如果cache不存在,查询数据库
1.2.2 自动加载同步
LoadingCache<String, User> userDaoCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS)
.expireAfterAccess(1, TimeUnit.SECONDS).maximumSize(10).build(key -> userDao.getOne(key))
User user = UserDaoCache.get("1");
1.2.3 自动加载异步
AsyncLoadingCache<String, User> asynUserDaoCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS).expireAfterAccess(1, TimeUnit.SECONDS).maximumSize(10)
.buildAsync(key -> userDao.getOne(key));
User user = asynUserDaoCache.get("1").get();
1.3. 其他API
1.3.1 统计缓存信息
Cache<Object, Object> cacheStats = Caffeine.newBuilder()
.expireAfterWrite(1000, TimeUnit.SECONDS)
.expireAfterAccess(1000, TimeUnit.SECONDS)
.recordStats()//记录统计信息
.weakKeys()//key弱引用
.weakValues()//value 弱引用
.maximumSize(10).build()
CacheStats stats = cacheStats.stats();//获取统计信息
1.3.2 CacheWriter
LoadingCache<String, User> cacheWriter = Caffeine.newBuilder().expireAfterAccess(3, TimeUnit.SECONDS)
.writer(new CacheWriter<String, User>() {
@Override
public void write(String id, User user) {
System.out.println("***写入***" + id);//当缓存数据时,调用此方案
}
@Override
public void delete(String id, User user, RemovalCause cause) {
System.out.println("***delete***" + id);//当手动删除数据时,调用此方法
}
}).build(id -> userDao.getOne(id));
1.3.3 RemovealListener监听(手动删除)
1.4. 缓存淘汰机制
常见缓存机制有:LRU(Least Recently Used)最近最少使用
LFU(Least Frequently Used)最不经常使用
1.4.1 LRU
如果一个数据在最近一段时间没有被访问到,那么认为在将来他被访问的可能性也很低,也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰
缺点是:注重对数据的使用时间是不是最近,缓存数目超过容量,缓存数据很容易淘汰
1.4.2 LFU
如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰
缺点:注重对元素的使用频次上是不是较大。
1.在短时间内如果某些元素访问频率很高,会导致后续很难淘汰,造成无效数据缓存。
2.突发流量的时候,可能由于没有及时达到足够的频率数据来保证自己驻留在缓存中,从而导致缓存的命中率下降
1.5 参数总结
maximumSize:设置缓存最大条目数,超过条目则触发回收
maximumWeight:设置缓存最大权重,设置权重是通过weigher方法, 需要注意的是权重也是限制缓存大小的参数,并不会影响缓存淘汰策略,也不能和maximumSize方法一起使用。
weakKeys:将key设置为弱引用,在GC时可以直接淘汰
weakValues:将value设置为弱引用,在GC时可以直接淘汰
softValues:将value设置为软引用,在内存溢出前可以直接淘汰
expireAfterWrite:写入后隔段时间过期
expireAfterAccess:访问后隔断时间过期
refreshAfterWrite:写入后隔断时间刷新
removalListener:缓存淘汰监听器,配置监听器后,每个条目淘汰时都会调用该监听器
writer:writer监听器其实提供了两个监听,一个是缓存写入或更新是的write,一个是缓存淘汰时的delete,每个条目淘汰时都会调用该监听器
2. @Cacheable注解
借鉴了https://blog.youkuaiyun.com/qq_35981283/article/details/82429603
cache方面的注解主要有一下5个
- @Cacheable 触发缓存入口(这里一般放在创建和获取的方法上)
- @CacheEvict 触发缓存的eviction(用于删除的方法上)
- @CachePut 更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)
- @Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)
- @CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)
项目中主要使用的是@Cacheable注解
源码
public @interface Cacheable {
/**
* 设定要使用的cache的名字,必须提前定义好缓存
*/
@AliasFor("cacheNames")
String[] value() default {};
/**
* 同value(),决定要使用那个/些缓存
*/
@AliasFor("value")
String[] cacheNames() default {};
/**
* 使用SpEL表达式来设定缓存的key,如果不设置默认方法上所有参数都会作为key的一部分
*/
String key() default "";
/**
* 用来生成key,与key()不可以共用
*/
String keyGenerator() default "";
/**
* 设定要使用的cacheManager,必须先设置好cacheManager的bean,这是使用该bean的名字
*/
String cacheManager() default "";
/**
* 使用cacheResolver来设定使用的缓存,用法同cacheManager,但是与cacheManager不可以同时使用
*/
String cacheResolver() default "";
/**
* 使用SpEL表达式设定出发缓存的条件,在方法执行前生效
*/
String condition() default "";
/**
* 使用SpEL设置出发缓存的条件,这里是方法执行完生效,所以条件中可以有方法执行后的value
*/
String unless() default "";
/**
* 用于同步的,在缓存失效(过期不存在等各种原因)的时候,如果多个线程同时访问被标注的方法
* 则只允许一个线程通过去执行方法
*/
boolean sync() default false;
}
项目中的使用