缓存填充策略
介绍:缓存的填充方式有三种,手动、同步和异步
手动加载
介绍:
手动控制缓存的增删改处理,主动增加、获取以及依据函数式更新缓存,底层使用ConcurrentHashMap进行节点存储,因此get方法是安全的。批量查找可以使getAllPresent()方法或者带填充默认值的getAll()方法
使用方式:
@Test
void test1() {
com.github.benmanes.caffeine.cache.@NonNull Cache<String, String> cache = Caffeine.newBuilder()
// 最大数量
.maximumSize(100)
// 设置缓存策略在1天未写入过期缓存
.expireAfterAccess(1, TimeUnit.DAYS)
.build();
// 存入缓存
cache.put("cl", "cl");
// 根据键值获取缓存
System.out.println(cache.getIfPresent("cl"));
// 根据键值获取缓存, 如果为空则调用后面的参是
System.out.println(cache.get("abc", this::buildLoader));
// 根据键值清除缓存
cache.invalidate("abc");
System.out.println(cache.getIfPresent("abc"));
}
// 缓存后面执行的方法
String buildLoader(String k) {
return k + "+default";
}
同步加载
介绍:
LoadingCache对象进行缓存的操作,使用CacheLoader进行缓存存储管理。 批量查找可以使用getAll()方法。 默认情况下, getAll()将会对缓存中没有值的key分别调用CacheLoader.load方法来构建缓存的值(build中的表达式)。我们可以重写CacheLoader.loadAll方法来提高getAll()的效率。
使用方式:
@Test
void test1() {
LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterAccess(1, TimeUnit.DAYS)
// getAll将会对缓存中, 没有值的key分别调用CacheLoader.load方法来构建缓存的值
.build(this :: buildLoader);
// 存储要添加的缓存
List<String> keys = new ArrayList<>();
keys.add("cl");
keys.add("b");
keys.add("c");
keys.add("d");
// 返回一个map
Map<String, String> cacheAll = loadingCache.getAll(keys);
System.out.println(cacheAll.get("b"));
System.out.println(cacheAll.get("c"));
System.out.println(cacheAll.get("a"));
}
// 缓存后面执行的方法
String buildLoader(String k) {
return k + "+default";
}
异步加载
介绍:
AsyncLoadingCache对象进行缓存管理,get()返回一个CompletableFuture对象,默认使用ForkJoinPool.commonPool()来执行异步线程,但是我们可以通过Caffeine.executor(Executor) 方法来替换线程池
注意: 异步和同步使用方式相似, 这里的话主要写一下创建和异步转同步
使用方式:
@Test
void test1() {
AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterAccess(1,TimeUnit.DAYS)
.buildAsync(k-> this.buildLoaderAsync(k).get());
try {
System.out.println(asyncLoadingCache.get("123").get());
} catch (Exception e) {
e.printStackTrace();
}
}
// 类似同步的load
CompletableFuture<String> buildLoaderAsync(String k) {
return CompletableFuture.supplyAsync(() -> k + "buildLoaderAsync");
}
转换使用方式:
AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterAccess(1,TimeUnit.DAYS)
.buildAsync(k -> this.buildLoaderAsync(k).get());
// 异步转同步
LoadingCache<String, String> cache = asyncLoadingCache.synchronous();
System.out.println("------------");
System.out.println(cache.get("dd"));
过期策略
介绍:
Caffeine的缓存清除是惰性的,可能发生在读请求后或者写请求后
比如说有一条数据过期后,不会立即删除,可能在下一次读/写操作后触发删除(类比于redis的惰性删除)。
如果读请求和写请求比较少,但想要尽快的删掉cache中过期的数据的话,
可以通过增加定时器的方法,定时执行cache.cleanUp()方法(异步方法,可以等待执行),触发缓存清除操作。
基于大小过期
介绍:当缓存超出后,使用W-TinyLFU算法进行缓存淘汰处理
使用方式:
maximumSize()方法,参数是缓存中存储的最大缓存条目,当添加缓存时达到条目阈值后,将进行缓存淘汰操作
// 使用方式
AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
.maximumSize(100)
基于权重过期
介绍:
通过权重来计算,每个实体都有不同的权重,总权重到达最高时淘汰实体。
weigher()方法可以指定缓存所占比重,maximumWeight()方法指定最大的权重阈值,当添加缓存超过规定权重后,进行数据淘汰
使用方式:
com.github.benmanes.caffeine.cache.@NonNull LoadingCache<String, String> cache = Caffeine.newBuilder()
.maximumWeight(10)
.weigher(new Weigher<Object, Object>() {
@Override
public @NonNegative int weigh(@NonNull Object o, @NonNull Object o2) {
System.out.println("key" + o + "value" + o2);
return 5;
}
})
.build(this::buildLoader);
List<String> list = Lists.newArrayList("c1", "c2", "c3");
cache.put(list.get(0), list.get(0));
System.out.println(list.get(0) + "--" + cache.get(list.get(0)));
cache.put(list.get(1), list.get(1));
System.out.println(list.get(1) + "---" + cache.get(list.get(1)));
System.out.println(cache.getAll(list));
基于时间过期
介绍:
expireAfterAccess():缓存访问后,一定时间失效;即最后一次访问或者写入开始时计时。
expireAfterWrite():缓存写入后,一定时间失效;以写入缓存操作为准计时。(在最后一次写入缓存后开始计时,在指定的时间后过期。)
expireAfter():自定义缓存策略,满足多样化的过期时间要求。
这里只展示after, 其他的话 使用方式大致相同,自己动手试试
注意:当expireAfterAccess和expireAfterWrite同时存在时,只有expireAfterWrite有效
使用方式:
com.github.benmanes.caffeine.cache.@NonNull LoadingCache<String, String> cache = Caffeine.newBuilder()
.expireAfter(new Expiry<Object, Object>() {
// 创建后过期
@Override
public long expireAfterCreate(@NonNull Object o, @NonNull Object o2, long l) {
return 1;
}
// 更新后过期
@Override
public long expireAfterUpdate(@NonNull Object o, @NonNull Object o2, long l, @NonNegative long l1) {
return 1;
}
// 读取后过期
@Override
public long expireAfterRead(@NonNull Object o, @NonNull Object o2, long l, @NonNegative long l1) {
return 1;
}
}).build(this::buildLoader);
基于引用回收
介绍:
使用方式:
基本使用
手动删除
使用方式:
自动刷新
使用方式:
refreshAfterWrite:这里设置的是1分钟后自动刷新
移除通知
使用方式:
外部存储
使用方式:
统计缓存使用情况
使用方式:
实例整合
编码方式
使用方式:
1、缓存配置类
@Configuration
public class CacheConfig {
@Bean
public Cache<String, Object> caffeineCache() {
return Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期
.expireAfterWrite(60, TimeUnit.SECONDS)
// 初始的缓存空间大小
.initialCapacity(100)
// 缓存的最大条数
.maximumSize(1000)
.build();
}
}
2、使用
注意:这里我只写了存入的写法, 如果要获取缓存,用cache的get缓存我们存储的key就可以了。
如:
// 以下代码的具体含义请看文章, 文章都有介绍,如果没有 可以评论 我会给你进行解答
cache.put(1,"测试缓存")
// 获取缓存
cache.get("1");
// 如果你使用get爆红, 那可能是因为你没有填写第二个,那么你可以这样做
cache.get("1", value -> value);
// 或者
cache.asMap().get("1");
示例:
private final Cache<Object, Object> cache;
public FeedbackServiceImpl( Cache<Object, Object> cache) {
this.cache = cache;
}
public void insertFeedBack(Feedback feedback) {
int i = feedbackMapper.insert(feedback);
cache.put(feedback.getId(), feedback);
}
注意:如果你只是简单的使用, 你可以使用末文的缓存工具类
注解方式(整合SpringBoot的cache)
介绍:
使用方式:
@Cacheable注解:
@CachePut注解:
@CacheEvict注解:
@CacheConfig注解:
// 一个类中可能会有多个缓存操作,而这些缓存操作可能是重复的。这个时候可以使用
@CacheConfig(cacheNames="emp") //抽取缓存的公共配置
@Caching注解:
示例:
// 开启缓存
@EnableCaching
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
// 其他方法使用和他一样, 具体可以看代码,来自己动动手试试看
@Cacheable(cacheNames = {"emp"},condition = "#id>0")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
简易缓存工具类
具体含义看代码注释
/**
* @author huangye 所谓致知在格物者,言欲致吾之知,在即物而穷其理也。
* @version 1.0 2020/10/4
*/
public class CacheUtil{
private static Cache<String, Object> cache;
static {
cache = createCache();
}
/**
* 插入缓存 | 更新缓存
* @param cacheName 缓存前缀
* @param key 键
* @param value 值
*/
public static void saveCache(String cacheName, String key, Object value) {
System.out.printf("存储缓存的数据 key(%s) value(%s)",cacheName + "-" + key, value);
System.out.println();
cache.asMap().put(cacheName + "-" + key, value);
}
/**
* 根据键获取缓存
* @param cacheName 缓存前缀
* @param key 键
* @return Object类型, 由使用者自己转换
*/
public static Object getCache(String cacheName, String key) {
System.out.printf("获取缓存的数据 key(%s) value(%s)",cacheName + "-" + key,
cache.asMap().get(cacheName + "-" + key) );
System.out.println();
return cache.asMap().get(cacheName + "-" + key);
}
/**
* 根据键值删除缓存
* @param cacheName 缓存前缀
* @param key 建
*/
public static void deleteCache(String cacheName, String key) {
System.out.printf("删除缓存的数据 key(%s)", cacheName + "-" + key);
System.out.println();
cache.asMap().remove(cacheName + "-" + key);
}
private static class CacheSingletonHolder {
private final static Cache<String, Object> CACHE = Caffeine.newBuilder()
// 初始的缓存空间大小
.initialCapacity(100)
// 缓存的最大条数
.maximumSize(1000)
// 最后一次缓存访问过后,7天后失效
.expireAfterAccess(7, TimeUnit.DAYS)
.build();
}
private static Cache<String, Object> createCache() {
return CacheSingletonHolder.CACHE;
}
}
ps:这里的图片都来源于我学习时做的笔记(下图位证), 因为有的是在网上找文章学习的,所以可能会遇到有的是和别人一样的内容,我是想着吧那些之前看的文章放到文章末尾的,但是我找不到了非常抱歉, 我这里的话只是想做个知识的汇总,如果有错误的地方,可以评论留言 我会改正,谢谢