Spring Cache

时间:2023-01-28 08:54:20

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、基础概念 

Spring Cache

 3、注解

Spring Cache

Spring Cache 

4、表达式语法 

Spring Cache

 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,我们称为缓存击穿。

 解决: 加锁