文章目录
一、概述
常见的缓存的框架有Redis
、Memcached
、Guava
、Caffeine
等等, 各有各的优势。如果我们的程序想要使用缓存,就要与这些框架耦合。聪明的架构师已经在利用接口来降低耦合了,利用面向对象的抽象和多态的特性,做到业务代码与具体的框架分离。但我们仍然需要显式地在代码中去调用与缓存有关的接口和方法,在合适的时候插入数据到缓存里,在合适的时候从缓存中读取数据。
Spring Cache
利用了AOP
,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。而且Spring Cache
也提供了很多默认的配置,用户可以3秒钟就使用上一个很不错的缓存功能。
二、Spring Cache 的使用
2.1 环境搭建
① 引入Spring Cache
坐标依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
② 配置:使用redis
作为缓存
spring:
cache:
type: redis
③ 开启缓存功能注解:@EnableCaching
通常 Spring Cache 注解都可以用在类上或者方法级别上,如果用在类上,就是对该类的所有 public 方法生效
2.2 缓存的读模式 @Cacheable
@Cacheble
注解一般用在查询方法上,表示这个方法有了缓存的功能,方法的返回值会被缓存下来。
- 如果缓存中有,方法不用调用,如果缓存中没有,才会调用方法
key
默认自动生成;缓存的名字:SimpleKey [](自主生成的key值)
- 缓存的
value
的值。默认使用jdk
序列化机制,将序列化后的数据存到redis
- 默认
ttl
时间 -1;
测试:
@Cacheable({"category"})
public List<CategoryEntity> getLevel1Categorys() {
......
}
我们发现缓存中的数据key
就是自动生成的SimpleKey []
,值是二进制数据。
2.3 自定义缓存配置
指定生成的缓存使用的key, key属性指定,接受一个SpEL
@Cacheable(value = {"category"},key = "#root.method.name")
public List<CategoryEntity> getLevel1Categorys() {
return this.baseMapper.selectList(
new QueryWrapper<CategoryEntity>().eq("parent_cid",0));
}
指定缓存的数据的存活时间: 配置文件中修改ttl
:
spring:
cache:
redis:
time-to-live: 3600000
将数据保存为json
格式, 自定义RedisCacheConfiguration
即可:
@Configuration
@EnableCaching
public class MyCacheConfig {
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//修改key和value的序列化机制
config = config .serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()));
config = config.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中的所有配置都生效
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
此时缓存中的数据:
其他配置:
spring:
cache:
type: redis
redis:
# 缓存前缀,如果指定了前缀,就用指定的,如果没有,就使用缓存的名字作为前缀
key-prefix: CACHE_
use-key-prefix: true
# 是否缓存null值,防止缓存穿透
cache-null-values: true
2.4 @CachePut
加了@CachePut
注解的方法,会把方法的返回值put
到缓存里面缓存起来,供其它地方使用。它通常用在新增方法上。
2.5 @CacheEvict 删除缓存
缓存的写模式:失效模式
@CacheEvict(value = "category",key = "'getLevel1Categorys'")
public void updateCascade(CategoryEntity category) {
......
}
2.6 @Caching 多个操作
@Caching(evict = {
@CacheEvict(value = "category",key = "'getLevel1Categorys'"),
@CacheEvict(value = "category",key = "'getCatelogJson'")
})
public void updateCascade(CategoryEntity category) {
......
}
三、Spring Cache 的不足
① 读模式
- 缓存穿透:查询一个null数据,开启缓存空数据配置
spring.cache.redis.cache-null-values=true
- 缓存击穿:大量并发进来查询一个正好过期的数据,解决:加锁
- 缓存雪崩:大量的
key
同时过期,解决:加过期时间,spring.cache.redis.time-to-live=3600000
② 写模式:缓存与数据库一致
- 读写加锁
- 引入
Canal
,感知到MySQL
的更新去更新数据库 - 读多写多,直接去数据库查询就行