美文网首页
SpringCache

SpringCache

作者: 乙腾 | 来源:发表于2020-12-01 21:21 被阅读0次

    Introduce

    Spring 3.1 引入了激动人心的基于凝视(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(比如EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中加入少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。

    springCache 是spring整合的

    image.png

    缓存官方文档

    https://docs.spring.io/spring-framework/docs/5.2.11.RELEASE/spring-framework-reference/integration.html#cache

    image.png

    Cache Abstraction 缓存抽象,如其名字,它本质上不是一个具体的缓存实现方案(比如EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中加入少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。

    不用SpringCache的缓存

     //缓存改写2:使用redis作为本地缓存
            ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
            String catalogJson = ops.get("catalogJson");
            if (StringUtils.isEmpty(catalogJson)) {
                Map<String, List<Catalog2Vo>> categoriesDb = getCategoriesDb();
                String toJSONString = JSON.toJSONString(categoriesDb);
                ops.set("catalogJson",toJSONString);
               return categoriesDb;
           }
           Map<String, List<Catalog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});
          return listMap;
    

    使用SpringCache

    pom

    <!--redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <exclusions>
            <exclusion>
                <groupId>io.lettuce</groupId>
                <artifactId>lettuce-core</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--jedis客户端-->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
    <!--缓存-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    

    redis.yml

    spring:
      redis:
        host: 192.168.16.129
        port: 6379
      cache:
        type: redis
        redis:
          time-to-live: 3600000
          #是否缓存空值,防止缓存穿透
          cache-null-values: true
    

    bootstrap-dev.properties

    spring.profiles.active=dev
    spring.application.name=gulimail-product-config
    spring.cloud.nacos.config.server-addr=xxxxxx:8848
    spring.cloud.nacos.config.file-extension=yaml
    spring.cloud.nacos.config.group=gulimail_product_group
    spring.cloud.nacos.config.namespace=72616c9f-6977-460d-9a1b-b43859b57beb
    
    spring.cloud.nacos.config.ext-config[0].data-id=gulimail-product-redis-dev.yaml
    spring.cloud.nacos.config.ext-config[0].group=gulimail_product_group
    spring.cloud.nacos.config.ext-config[0].refresh=true
    

    配置类

    package com.pl.gulimail.product.config;
    
    import org.springframework.boot.autoconfigure.cache.CacheProperties;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    @EnableConfigurationProperties(CacheProperties.class)//不加这个注解,配置文件不生效
    @Configuration
    @EnableCaching
    public class MyCacheConfig {
        //要想配置文件生效 @EnableConfigurationProperties(CacheProperties.class)
        @Bean
        public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
    
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
            //序列化key value
            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;
        }
    }
    
    

    开启缓存

    @EnableCaching

    package com.pl.gulimail.product;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @EnableCaching
    @EnableAspectJAutoProxy(exposeProxy = true)
    @MapperScan("com.pl.gulimail.product.sql.dao")
    @EnableDiscoveryClient
    @SpringBootApplication
    @EnableFeignClients(basePackages = {"com.pl.gulimail.product.sql.feign"})
    public class GulimalProductApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(GulimalProductApplication.class, args);
        }
    
    }
    
    

    测试使用缓存

    • @Cacheable: Triggers cache population. 处罚将数据保存到缓存的操作。
    • @CacheEvict: Triggers cache eviction. 触发将数据从缓存中删除的操作。
    • @CachePut: Updates the cache without interfering with the method execution. 不影响方法执行更行缓存。
    • @Caching: Regroups multiple cache operations to be applied on a method. 组合史上多个操作。
    • @CacheConfig: Shares some common cache-related settings at class-level.在类级别共享缓存配置。

    code

    读模式

    //value 缓存分区 按照业务类型分区,
    //@Cacheable(value = {"category","sss"},key = "#root.methodName",sync = true)  这条数据往category,sss两个区中存放数据
    @Cacheable(value = {"category"})
    public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithSpringCache() {
        return getCategoriesDb();
    }
    

    SimpleKey是默认给的名字,数据是jdk序列化的样子


    image.png

    指定key并引入配置类,value数据为json格式后

    //value 缓存分区 按照业务类型分区,
    //@Cacheable(value = {"category","sss"},key = "#root.methodName",sync = true)  这条数据往category,sss两个区中存放数据
    @Cacheable(value = {"category"},key = "#root.methodName",sync = true)
    public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithSpringCache() {
        return getCategoriesDb();
    }
    

    可以发现key为方法名,TTL失效时间也按照配置文件中指定的一样生效了。该方法只有在第一次访问或者缓存失效后会被调用查询数据库,其余时间直接从缓存中读取数据。

    image.png
    image.png

    notice:

    n1使用@Cacheable的特点:

    1.如果方法中有姐不在调用了。

    2.缓存的数据,默认的key是默认生成的,缓存名字:: SimpleKey(自动生成的key值)

    3.缓存的value,默认使用jdk序列化机制,将序列化后的数据缓存到redis。

    4.默认ttl过期时间 -1。永不过期。

    自定义:

    1.指定生成缓存中的key(注解上指定key)

    2.指定缓存存活时间

    3.将数据保存为json格式

    key:

    image.png

    支持SqEL动态获取key,

    如果想用方法名作为key

     #root.methodName
    

    如果想获取方法的参数
    {@code #root.args[1]}, {@code #p1} or {@code #a1}

    配置文件中指定前缀

    image.png
    测试缓存空值和指定前缀
    image.png
    不使用缓存前缀
    image.png

    没有前缀,只显示指定的key

    image.png

    写模式

    指定删除指定的一个key

    /**
    * 级联更新所有数据
    * CacheEvict: 失效模式
    * @param category
    */
    @Override
    @Transactional
    @CacheEvict(value = {"category"},key = "'getCatalogJsonDbWithSpringCache'") //指定删除哪个分区,哪个key
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
    }
    

    key是遵循SqEL表达式的,字符串需要加单引号。

    执行更行操作

    image.png image.png

    指定删除多个key

    /**
    * 级联更新所有数据
    * CacheEvict: 失效模式
    * @param category
    */
    @Override
    @Transactional  
    @Caching(evict = {
    @CacheEvict(value = {"category"},key = "'getCategoryLeve1'"),
    @CacheEvict(value = {"category"},key = "'getCatalogJsonDbWithSpringCache'")
    })
    public void updateCascade(CategoryEntity category) {
      this.updateById(category);
    }
    

    指定删除区中的的所有key

    /**
    * 级联更新所有数据
    * CacheEvict: 失效模式
    * @param category
    */
    @Override
    @Transactional
    @CacheEvict(value = {"category"},key = "'getCatalogJsonDbWithSpringCache'",allEntries = true)
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
    }
    

    双写模式

    这次更新完事后,将数据放到缓存中一份,方法中必须有返回值

    /**
    * 级联更新所有数据
    * CacheEvict: 失效模式
    * @param category
    */
    @Override
    @Transactional
    @CacheEvict(value = {"category"},key = "'getCatalogJsonDbWithSpringCache'",allEntries = true)
    @cachePut
    public CategoryEntity updateCascade(CategoryEntity category) {
        return this.updateById(category);
    }
    

    Spring-Cache的不足之处

    1.读模式(@Cacheable)

    缓存穿透:查询一个null数据。解决方案:缓存空数据,可通过spring.cache.redis.cache-null-values=true

    缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:加锁 ? 默认是无加锁的;

    使用sync = true来解决击穿问题

    缓存雪崩:大量的key同时过期。解决:加随机时间。加上过期时间,即使通过配置文件指定了相同的随机时间,但是每次出发写缓存的时间都不一致,所以就算TTL时间相同,但是彼此失效的时间不同。

    可以看出Spring-Cache对读操作的所有问题,都是有对应的解决办法的。

    notice:

    只有@Cacheable有锁,其他注解没有加锁的属性。

    <wiz_code_mirror><pre class=" CodeMirror-line " role="presentation">@Cacheable(value = {"category"},key = "#root.methodName",sync = true)</pre></wiz_code_mirror>

    2.写模式:(缓存与数据库一致)

    因为@CacheEvict,@CachePut都没有锁属性,所以都不可以加锁,这些注解对于缓存雪崩的场景是没法应对的。

    解决办法:

    a、读写加锁。

    b、引入Canal,感知到MySQL的更新去更新Redis

    c 、读多写多,直接去数据库查询就行

    3.总结:

    常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):

    写模式(只要缓存的数据有过期时间就足够了)

    特殊数据:特殊设计

    相关文章

      网友评论

          本文标题:SpringCache

          本文链接:https://www.haomeiwen.com/subject/eplewktx.html