本地缓存介绍
缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。例如Redis 这种 NoSql数据库作为缓存组件,它能够很好的作为分布式缓存组件提供多个服务间的缓存,但是 Redis 这种还是需要网络开销,增加时耗。本地缓存是直接从本地内存中读取,没有网络开销,例如秒杀系统或者数据量小的缓存等,比远程缓存更合适。
Caffeine缓存组件介绍
按 Caffeine Github 文档描述,Caffeine 是基于 JAVA 8 的高性能缓存库。并且在 spring5 (springboot 2.x) 后,spring 官方放弃了 Guava,而使用了性能更优秀的 Caffeine 作为默认缓存组件。Caffeine作为当下本地缓存的王者被大量应用在各个实际项目中,可以有效的提高服务的吞吐量、qps、降低rt。
整合过程
配置
依赖
<!--Spring的 @Cacheable等注解相关-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--caffeine 缓存的使用姿势-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
缓存管理器的配置
/**
* 定义缓存管理器,配合Spring Cache的注解使用
* @return
*/
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
//设置过期时间
.expireAfterAccess(5, TimeUnit.MINUTES)
//设置初始化容量
.initialCapacity(10)
//设置最大容量
.maximumSize(100));
return cacheManager;
}
请注意,除了使用上述方式配置缓存管理器外,还可以通过配置文件的形式配置(推荐),通过配置文件的形式如下
spring:
cache:
type: caffeine
caffeine:
spec: initialCapacity=10,maximumSize=100,expireAfterWrite=5m //参数所代表的意义如上配置类等价
使用
配置完毕后,我们就可以在项目中使用本地缓存了。
缓存使用示例
使用缓存前,我们还需要在项目的启动类上加上 @EnableCaching注解,表示启动缓存。
1、@Cacheable
这个注解用于修饰方法,当我们访问该注解修饰的方法时,若缓存存在则返回缓存,若缓存不存在,则执行方法,并将方法的返回结果放入缓存。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
....
}
以上是该注解最重要的三个属性,其中cacheName和value等价,cacheName可以理解为缓存key的前缀(类似于redis中缓存key的order:userId的order:)。当key不设置值时,默认使用方法参数进行初始化,注意key为SpEl表达式,因此如果key写为字符串时,需要单引号括起来。如下为一个简单的使用示例。
/**
* 类比redis:
* cacheName + key = redisKey(redis中的缓存key)
* 方法返回结果: key对应的缓存值
* @param age 参数
* @return
*/
@Cacheable(key = "#age",cacheNames = "cache")
public String getCache(int age) {
return "get"+age+"--->" + UUID.randomUUID().toString().substring(6);
}
例如我们传的age为11,那么缓存的key就为 cache::11
。除了以上的属性外,该注解还有condition
属性比较常用,用于判断缓存保存的条件。
@Cacheable(key = "#age",cacheNames = "cache",condition = "#age % 2 == 0")
public String getCache(int age) {
return "get"+age+"--->" + UUID.randomUUID().toString().substring(6);
}
上面这个情况,当age为偶数时,才会走缓存,否则不走缓存,也就不会写入缓存。
还有一个unless
属性作用和condition
相反,请自行测试。
2、@CachePut
不管当前缓存有没有,都将方法的返回结果加入缓存。不用多说,大家就知道这个注解常用于修改数据时,更新缓存的场景。
/**
* 不管缓存有没有,都写入缓存
* @param age
* @return
*/
@CachePut(key = "#age",cacheNames = "cache")
public String putCache(int age) {
return "put"+age+"--->" + UUID.randomUUID().toString().substring(6);
}
3、@CacheEvict
这个注解用于删除缓存
/**
* 根据cacheName和key使缓存失效,即删除缓存
* @param age
* @return
*/
@CacheEvict(key = "#age",cacheNames = "cache")
public String delCache(int age) {
return "del"+age+"--->" + UUID.randomUUID().toString().substring(6);
}
4、@Caching
在实际的项目场景中,常常需要我们修改多个缓存,对于这个场景,我们就需要用到@Caching
注解了,他可以用来组合多个注解。
/**
* caching实现组合注解,添加缓存,并失效其他缓存
* @param age
* @return
*/
@Caching(cacheable = @Cacheable(cacheNames = "caching",key = "#age"),evict = @CacheEvict(cacheNames = "t1",key = "#age"))
public String caching(int age) {
return "caching"+age+"--->" + UUID.randomUUID().toString().substring(6);
}
以上代码就是组合操作:
- 从caching::age缓存取数据,缓存不存在时,向缓存放数据
- 失效缓存t1:age
5、出现异常时,缓存会怎么样
以上的几种情况都是正常工作的场景,当方法抛出异常时,这个缓存的表现会如何呢?
/**
* 用于测试异常情况
* @param age
* @return
*/
@Cacheable(cacheNames = "cache",key = "#age")
public int testE(int age) {
return 100/age;
}
经过实测,当age == 0
时,以上代码的缓存不会成功。