本地进程缓存
缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类:
1、分布式缓存,例如Redis:
优点:存储容量更大、可靠性更好、可以在集群间共享
缺点:访问缓存有网络开销
场景:缓存数据量较大、可靠性
要求较高、需要在集群间共享
2、进程本地缓存,例如HashMap、GuavaCache:
优点:读取本地内存,没有网络开销,速度更快
缺点:存储容量有限、可靠性较低、无法共享
场景:性能要求较高,缓存数据量较小
Caffeine介绍
Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。
Caffeine相当于一个缓存工厂,可以创建出多个缓存实例 Cache。
这些缓存实例都继承了 Caffeine 的参数配置,Caffeine 是如何配置的,这些缓存实例就具有什么样的特性和功能。
Caffeine 是目前性能最好的本地缓存,目前Spring内部的缓存使用的就是Caffeine。
GitHub地址:https://github.com/ben-manes/caffeine
Caffeine缓存属性
1. initialCapacity 缓存初始容量
整数,表示能存储多少个缓存对象。
为什么要设置初始容量呢?
如果提前能预估缓存的使用大小,那么可以设置缓存的初始容量,以免缓存不断地进行扩容,致使效率不高。
2. maximumSize 最大容量
如果缓存中的数据量超过这个数值,Caffeine 会有一个异步线程来专门负责清除缓存,按照指定的清除策略来清除掉多余的缓存。
3. maximumWeight 最大权重
存入缓存的每个元素都要有一个权重值,当缓存中所有元素的权重值超过最大权重时,就会触发异步清除。
缓存状态收集器
默认的缓存状态收集器(CacheStats)
缓存的状态会用一个 CacheStats 对象记录下来,通过访问 CacheStats 对象就可以知道当前缓存的各种状态指标。
什么是加载?
当查询缓存时,缓存未命中,那就需要去第三方数据库中查询,然后将查询出的数据先存入缓存,再返回给查询者,这个过程就是加载。
Caffeine过期策略
在Caffeine中分为两种缓存,一个是有界缓存,一个是无界缓存,无界缓存不需要过期并且没有界限。
在有界缓存中提供了三个过期API:
1. expireAfterWrite:代表着写了之后多久过期
2. expireAfterAccess:代表着最后一次访问了之后多久过期
3. expireAfter:在expireAfter中需要自己实现Expiry接口,这个接口支持create,update,以及access了之后多久过期。注意这个API和前面两个API是互斥的。
Caffeine更新策略
设定多长时间后会自动刷新缓存。
Caffeine提供了refreshAfterWrite()方法来实现多久更新策略。
Caffeine打点监控
通过 'recordStats()Api' 开启,在`StatsCounter`接口中,定义了打点监控的方法。
例如:
1. recordHits:记录缓存命中
2. recordMisses:记录缓存未命中
3. recordLoadSuccess:记录加载成功(指的是CacheLoader加载成功)
4. recordLoadFailure:记录加载失败
5. recordEviction:记录淘汰数据
通过上面的监听,我们可以实时监控缓存当前的状态,以评估缓存的健康程度以及缓存命中率等,方便后续调整参数。
Caffeine淘汰监听
编写一个监听器
Cache<String, String> cache = Caffeine.newBuilder().removaListener(((key, value, cause) -> {
System.out.println(cause);
})).build();
淘汰原因如下:
1. EXPLICIT:用户造成的,通过调用remove方法删除。
2. REPLACED: 更新的时候,相当于把之前的value给删了。
3. COLLECTED: 用于垃圾收集器,也就是减少的软引用,弱引用。
4. EXPIRED:过期淘汰。
5. SIZE: 大小淘汰,当超过最大的时候就会进行淘汰。
Caffeine示例
@Test
void testBasicOps() {
// 构建cache对象
Cache<String, String> cache = Caffeine.newBuilder().build();
// 存数据
cache.put("gf", "西施");
// 取数据
String gf = cache.getIfPresent("gf");
System.out.println("gf = " + gf);
// 取数据,包含两个参数:
// 参数一:缓存的key
// 参数二:Lambda表达式,表达式参数就是缓存的key,方法体是查询数据库的逻辑
// 优先根据key查询JVM缓存,如果未命中,则执行参数二的Lambda表达式
String defaultGF = cache.get("defaultGF", key -> {
// 根据key去数据库查询数据
return "貂蝉";
});
System.out.println("defaultGF = " + defaultGF);
}
Caffeine既然是缓存的一种,肯定需要有缓存的清除策略,不然的话内存总会有耗尽的时候。
Caffeine提供了三种缓存驱逐策略:
1、基于容量:设置缓存的数量上限
// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1) // 设置缓存大小上限为 1
.build();
2、基于时间:设置缓存的有效时间
// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
// 设置缓存有效期为 10 秒,从最后一次写入开始计时
.expireAfterWrite(Duration.ofSeconds(10))
.build();
3、基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用
注意:在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或者在空闲时间完成对失效数据的驱逐。
redis和caffeine的区别?
相同点:都是缓存的方式
不同点:
1. redis是将数据存储到内存里;caffeine是将数据存储在本地应用里
2. caffeine和redis相比,没有了网络IO上的消耗