缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类:
-
分布式缓存,例如Redis:
-
优点:存储容量更大、可靠性更好、可以在集群间共享
-
缺点:访问缓存有网络开销
-
场景:缓存数据量较大、可靠性要求较高、需要在集群间共享
-
-
进程本地缓存,例如HashMap、GuavaCache:
-
优点:读取本地内存,没有网络开销,速度更快
-
缺点:存储容量有限、可靠性较低、无法共享
-
场景:性能要求较高,缓存数据量较小
-
这里使用的是 Caffeine
Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。GitHub地址:https://github.com/ben-manes/caffeine
Caffeine的性能非常好,下图是官方给出的性能对比:
如何使用:
1 导入依赖 想要使用一个完整的第三地方技术第一步肯定是导依赖
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
2 基本的api:
@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);
}
3 缓存的失效策略
Caffeine既然是缓存的一种,肯定需要有缓存的清除策略,不然的话内存总会有耗尽的时候。
Caffeine提供了三种缓存驱逐策略:
基于容量:设置缓存的数量上限 和redis差不多当缓存内元素个数超过时,通过基于就近度和频率的算法来驱逐掉不会再被使用到的元素
// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1) // 设置缓存大小上限为 1
.build();
基于时间:设置缓存的有效时间
// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
// 设置缓存有效期为 10 秒,从最后一次写入开始计时
.expireAfterWrite(Duration.ofSeconds(10))
.build();
-
基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。
注意:在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或者在空闲时间完成对失效数据的驱逐。
需求
利用Caffeine实现下列需求:
-
给根据id查询商品的业务添加缓存,缓存未命中时查询数据库
-
给根据id查询商品库存的业务添加缓存,缓存未命中时查询数据库
-
缓存初始大小为100
-
缓存上限为10000
1 定义两个Caffeine的缓存对象,分别保存商品、库存的缓存数据 配置到spring容器中方便使用
@Configuration
public class CaffeineConfig {
@Bean
public Cache<Long, Item> itemCache(){
return Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(10_000)
.build();
}
@Bean
public Cache<Long, ItemStock> stockCache(){
return Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(10_000)
.build();
}
}
使用这个
@Autowired
private Cache<Long, Item> itemCache;
@Autowired
private Cache<Long, ItemStock> stockCache;
在编写查询业务的时候就可以实现先从缓存中查询 如果缓存没有 就去数据库中查询 然后把查询的结果保存到缓存中 当再次的请求这条数据直接走jvm缓存 而不是走数据库 大大提升了效率
比如这个:
@GetMapping("/{id}")
public Item findById(@PathVariable("id") Long id) {
return itemCache.get(id, key -> itemService.query()
.ne("status", 3).eq("id", key)
.one()
);
}