20. 谷粒商城Spring Cache

介绍

Spring 从 3.1 开始定义了 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 接口来统一不同的缓存技术;并支持使用 JCache(JSR-107)注解简化我们开发;

Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCacheEhCacheCacheConcurrentMapCache 等;

每次调用需要缓存功能的方法时,Spring 会检查指定参数指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

使用 Spring 缓存抽象时我们需要关注以下两点;

  1. 确定方法需要被缓存以及他们的缓存策略
  2. 从缓存中读取之前缓存存储的数据

在这里插入图片描述

缓存管理器CacheManager定义规则, 真正实现缓存CRUD的是缓存组件,如ConcurrentHashMapRedis

引入依赖

        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        
        <!-- redis已经引过,此处就不写了-->

写配置

自动配置:

  • CacheAutoConfiguration 会导入 RedisCacheConfiguration;
  • 会自动装配缓存管理器 RedisCacheManager;

手动配置:

#spring
  cache:
    type: redis	# 使用redis作为缓存

注解

在这里插入图片描述

@Caching组合@Cachable、@CacheEvice、@CachePut
@CacheConfig在类级别共享缓存的相同配置

在这里插入图片描述

测试使用缓存

开启缓存功能

启动类上添加@EnableCaching注解

使用直接完成缓存操作

为需要使用缓存功能的方法添加@Cacheable({"category"})注解,并指定分区(最好按照业务进行分区)

它实现的效果就是,每次调用需要缓存功能的方法时,Spring 会检查指定参数指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

默认行为

  1. 如果缓存中有,不会调用方法。
  2. key默认自动生成:缓存的名字::SimpleKey [](自主生成的key值)。
  3. 缓存的velue值。默认使用jdk序列化机制。将序列化后的数据存到redis
  4. 默认ttl时间 -1

表达式语法

在这里插入图片描述

自定义

  1. 指定生成的缓存使用的key:

    key 属性,接收一个SpEL表达式

    	// 使用指定的值作为key
    	@Cacheable(value = {"categories"}, key = "'level1Categories'")
    	
    	// 使用方法名作为key
        @Cacheable(value = {"categories"}, key = "#root.method.name")	
    
  2. 指定缓存数据的存活时间

    配置文件中修改

    spring
      cache:
        redis:
          time-to-live: 3600000	# 单位ms
    

自定义缓存配置

我们希望将数据保存为json格式,需要修改SpringCache的自定义配置,这个比较麻烦,先看一下它的加载过程

原理

在这里插入图片描述

配置类

@EnableConfigurationProperties(CacheProperties.class)   // 开启属性配置的绑定功能,否则会导致配置文件失效
@Configuration
@EnableCaching  // 开启缓存功能,从启动类中移出来
public class SpringCacheConfig {
    @Bean
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // 序列化key
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()));

        // 序列化value
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericFastJsonRedisSerializer()));

        // 其它的则还是使用默认配置
        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:
    redis:      
      key-prefix: CACHE_	# 指定key的前缀,不指定则使用缓存的名字作为前缀
      use-key-prefix: true	# 是否使用前缀
      cache-null-values: true	# 是否缓存空值,可以防止缓存穿透

删除缓存分区的数据

分区数据

    /**
     * 查询一级分类
     * @return
     */
	@Override
    @Cacheable(value = {"category"}, key = "'level1Categories'")
    public List<CategoryEntity> findLevel1Categories() {
    }        

    /**
     * 查询全部分类
     * 使用SpringCache缓存版本
     * 只需要操作数据库,不需要关心缓存,一个注解就够了
     * @return
     */
    @Cacheable(value = {"category"}, key = "'categoryJson'")
    @Override
    public Map<Long, List<Category2VO>> getCategoryJson() {
        // 查出所有分类
        List<CategoryEntity> allCategories = this.list();
        List<CategoryEntity> l1Categories = listByPrentCid(allCategories, 0L);

        Map<Long, List<Category2VO>> categoryMap = l1Categories.stream().collect(Collectors.toMap(k1 -> k1.getCatId(), v1 -> {
            List<CategoryEntity> l2Categories = listByPrentCid(allCategories, v1.getCatId());
            List<Category2VO> category2VOs = null;

            if (l2Categories != null && l2Categories.size() > 0) {
                category2VOs = l2Categories.stream().map(l2 -> {
                    // 根据当前2级分类查出所有3级分类
                    List<CategoryEntity> l3Categories = listByPrentCid(allCategories, l2.getCatId());
                    List<Category3VO> category3VOs = null;
                    if (l3Categories != null && l3Categories.size() > 0) {
                        category3VOs = l3Categories.stream().map(l3 -> new Category3VO(l2.getCatId(),
                                l3.getCatId(), l3.getName())).collect(Collectors.toList());
                    }

                    return new Category2VO(v1.getCatId(), category3VOs, l2.getCatId(), l2.getName());
                }).collect(Collectors.toList());
            }
            return category2VOs;

        }));

        return categoryMap;
    }

删除一个分区的缓存数据

    // 删除一个分区的缓存数据
    @CacheEvict(value = "category", key = "'level1Categories'")
    public void updateDetail(CategoryEntity category) {
    }

删除多个分区的缓存数据

	// 删除多个分区的缓存数据
    @Caching(evict = {
            @CacheEvict(value = "category", key = "'level1Categories'"),
            @CacheEvict(value = "category", key = "'categoryJson'"),
    })    

删除指定分区的缓存数据

	// 删除某个分区下的缓存数据,也就是清除模式
	@CacheEvict(value = "category",allEntries = true)
	// 如果希望修改完数据,再往缓存里放一份,也就是双写模式,可以使用这个注解
	@CachePut

SpringCache原理与不足

读模式

缓存穿透

解决:保存空值

spring
  cache:
    redis:      
      cache-null-values: true	

缓存击穿

解决:加锁,默认不加锁

    @Cacheable(sync = true)

缓存雪崩

其实只要不是超大型并发,比如十几W的key同时过期,正好碰上十几W的请求过来查询,不用考虑这个问题

原来的解决方案是加随机时间,但是很容易弄巧成拙,假设数据不是同一时间放进去的,比如第1个数据是3秒过期,然后加了个随机数1秒,4秒过期,第2个数据是2秒过期,然后加了个随机数2秒,还是4秒过期,本来什么都不加的时候,它们过期时间不会冲撞在一起,结果有可能一加,倒还让他们撞在一起了,当时每一个数据存储的时间节点其实是不一样的,所以,只要指定过期时间就行

解决:加上过期时间

spring
  cache:
    redis:
      time-to-live: 3600000	# 单位ms

写模式

  1. 读写加锁
  2. 引入Cannel,感知MySQL的更新去更新数据库
  3. 读多写多,直接查询数据库

原理

CacheManager -> Cache -> Cache负责缓存的读写

结论

普通数据

  • 读多写少、及时性、一致性要求不高的数据,完全可以使用Spring Cache
  • 写模式,只要缓存的数据有过期时间,就足够了

特殊数据

  • 特殊设计
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值