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文件导入

    org.springframework.boot    spring-boot-starter-cache
    redis.clients    jedis

启动类开启缓存

@SpringBootApplication@EnableDiscoveryClient@EnableFeignClients@EnableCachingpublic class GulimallProductApplication {    public static void main(String[] args) {        SpringApplication.run(GulimallProductApplication.class, args);    }}

写配置

1)自动配置了那些
CacheAutoConfiguration导入了RedisCacheConfiguration
自动配好了缓存管理器RdisCacheManager

//将配置文件中的所有配置都生效@EnableConfigurationProperties(CacheProperties.class)@Configuration@EnableCachingpublic 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=10001spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848spring.cache.type=REDISspring.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 getLevel1Categorys() {        QueryWrapper wrapper = new QueryWrapper();        wrapper.eq("parent_cid",0);        List 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,我们称为缓存击穿。

 解决: 加锁