1、简介
Spring 从 3.1 开始定义了 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术; 并支持使用 JCache(JSR-107)注解简化我们开发;
Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache , EhCacheCache , ConcurrentMapCache 等;
每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已 经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓 存结果后返回给用户。下次调用直接从缓存中获取。
使用 Spring 缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
2、基础概念
3、注解
4、表达式语法
5、项目使用
pom文件导入
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
启动类开启缓存
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCaching
public class GulimallProductApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallProductApplication.class, args);
}
}
写配置
1)自动配置了那些
CacheAutoConfiguration导入了RedisCacheConfiguration
自动配好了缓存管理器RdisCacheManager
//将配置文件中的所有配置都生效
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
RedisCacheConfiguration config= RedisCacheConfiguration.defaultCacheConfig();
// config.entryTtl()
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;
}
}
2) 配置使用redis作为spring.cache.type=REDIS
# 应用名称
spring.application.name=gulimall-product
# 应用服务 WEB 访问端口
server.port=10001
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cache.type=REDIS
spring.cache.redis.time-to-live=3600000
#如果指定了前缀的值就用我们指定的前缀,如果没有就默认使用缓存的名字作为前缀
#spring.cache.redis.key-prefix=CACHE_
#spring.cache.redis.use-key-prefix=true
#缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true
业务代码使用
@Cacheable (value = {"category"},key = "#root.method.name",sync = true)
@Override
public List<CategoryEntity> getLevel1Categorys() {
QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
wrapper.eq("parent_cid",0);
List<CategoryEntity> categoryEntities = baseMapper.selectList(wrapper);
return categoryEntities;
}
//每一个缓存的数据需要我们来指定要放到那个名字的缓存【缓存的分区(按照业务类型区分)】 //代表当前办法的结果需要缓存,如果缓存中有,方法不调用,如果缓存中没有,会调用方法,最后将方法的结果返回
@CacheEvict(value ="category",allEntries = true )//删除分区全部缓存
@Override
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
}
@Caching(evict = { @CacheEvict(value = "category",key = "'getLevel1Categorys'"), @CacheEvict(value = "category",key = "'getCatalogJson'") })
上面整个代码意思是category的getLevel1Categorys和getCatalogJson的数据都需要修改
/** * @Cacheable ({"category"}) * 代表当前办法的结果需要缓存,如果缓存中有,方法不调用 * 如果缓存中没有,会调用方法,最后将方法的结果返回 * 默认行为: * 1)如果缓存中有,方法不调用 * key是默认自动生成的,缓存的名字:SimpleKey【】自动生成的key * value是jdk序列化机制,将序列化后的数据存到redis * 默认过期时间ttl -1 * 自定义 * 1)指定生成的缓存使用的key key属性指定(可以使用表达:key = "#root.method.name") * 2)指定缓存的数据的存活时间 配置文件指定 spring.cache.redis.time-to-live=3600000毫秒为单位 * 3)将数据保存为json格式 * CacheAutoConfiguration->RedisCacheConfiguration * ->自动配置了RedisCacheManage->初始化所有缓存->每个缓存决定使用什么配置 * ->如果redisCacheConfiguration有就用己有的,没有就用默认的 * ->想改缓存配置,只需要给容器中放一个RedisCacheConfiguration * spring-Cache的不足: * 1)读模式 * 缓存穿透:查询null,解决:缓存空数据 cahe-null-values=true * 缓存击穿:大量并发查询一个整过期的数据, 解决:加锁 sync = true * 缓存雪崩:大量过期的key同时过期 解决:加随机时间 * 2)写模式 * 1)读写加锁 * 2)引入canal,感知mysql的更新的数据库 * 3)读多写多 直接去数据库查询就可以 * 总结:常规数据(读多写少的,一致性要求不高的数据)完全可以用spring-Cache * 特殊数据特殊处理 *
缓存失效问题
先来解决大并发读情况下的缓存失效问题;
1、缓存穿透
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数 据库也无此记录,我们没有将这次查询的 null 写入缓存,这将导致这个不存在的数据每次 请求都要到存储层去查询,失去了缓存的意义。
在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,这就是 漏洞。
解决: 缓存空结果、并且设置短的过期时间。
2、缓存雪崩
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失 效,请求全部转发到 DB,DB 瞬时压力过重雪崩。
解决:
原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的 重复率就会降低,就很难引发集体失效的事件。
3、缓存击穿
对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问, 是一种非常“热点”的数据。
这个时候,需要考虑一个问题:如果这个 key 在大量请求同时进来前正好失效,那么所 有对这个 key 的数据查询都落到 db,我们称为缓存击穿。
解决: 加锁