• Spring Cache的基本使用与分析


    概述

    使用 Spring Cache 可以极大的简化我们对数据的缓存,并且它封装了多种缓存,本文基于 redis 来说明。

    基本使用

    1、所需依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    

    2、配置文件

    spring:
      # redis连接信息
      redis:
        host: 192.168.56.10
        port: 6379
      cache:
        # 指定使用的缓存类型
        type: redis
        # 过期时间
        redis:
          time-to-live: 3600000
          # 是否开启前缀,默认为true
          use-key-prefix: true
          # 键的前缀,如果不配置,默认就是缓存名cacheNames
          key-prefix: CACHE_
          # 是否缓存空置,防止缓存穿透,默认为true
          cache-null-values: true
    

    3、Spring Cache 提供的注解如下,使用方法参见:官方文档,通过这些注解,我们可以方便的操作缓存数据。

    • @Cacheable:触发缓存写入的操作
    • @CacheEvict:触发缓存删除的操作
    • @CachePut:更新缓存,而不会影响方法的执行
    • @Caching:重新组合要应用于一个方法的多个缓存操作,即对一个方法添加多个缓存操作
    • @CacheConfig:在类级别共享一些与缓存有关的常见设置

    例如,如果需要对返回结果进行缓存,直接在方法上标注 @Cacheable 注解(在主配置类上需要标注上 @EnableCaching

    @Cacheable(cacheNames = "userList") //指定缓存的名字,便于区分不同缓存
    public List<User> getUserList() {
    	...
    } 
    

    4、redis 默认使用 jdk 序列化,需要我们配置序列化机制,自定义一个配置类,否则存入的数据显示乱码

    @EnableCaching //开启缓存
    @Configuration
    public class MyCacheConfig {
        @Bean
        public RedisCacheConfiguration redisCacheConfiguration(){
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
            //指定键和值的序列化机制
            config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
            config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
            return config;
        }
    }
    

    5、使用以上配置后,虽然乱码的问题解决了,但配置文件又不生效了,比如过期时间等,这是因为在初始化时会判断用户是否自定义了配置文件,如果自定义了,原来的就不会生效,源码如下:

    private org.springframework.data.redis.cache.RedisCacheConfiguration
        determineConfiguration(ClassLoader classLoader) {
        //如果配置了,就返回自定义的配置
        if (this.redisCacheConfiguration != null) {
            return this.redisCacheConfiguration;
        }
    	//没配置使用默认的配置
        Redis redisProperties = this.cacheProperties.getRedis();
        org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
            .defaultCacheConfig();
        config = config.serializeValuesWith(
            SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
        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;
    }
    

    6、所以,我们也需要手动获取 ttl、prefix 等属性,直接仿照源码就行,将配置类修改为如下:

    @EnableCaching //开启缓存
    @Configuration
    @EnableConfigurationProperties(CacheProperties.class) //缓存的所有配置属性都在这个类里
    public class MyCacheConfig {
    
        @Bean
        public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
            //获取默认配置
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
            //指定键和值的序列化机制
            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;
        }
    }
    

    原理分析

    在 Spring 中 CacheManager 负责创建管理 Cache,Cache 负责缓存的读写,因此使用 redis 作为缓存对应的就有 RedisCacheManager 和 RedisCache。

    打开 RedisCache 源码,我们需要注意这两个方法:

    1、读取数据,未加锁

    @Override
    protected Object lookup(Object key) {
       byte[] value = cacheWriter.get(name, createAndConvertCacheKey(key));
        
       if (value == null) {
          return null;
       }
        
       return deserializeCacheValue(value);
    }
    

    2、读取数据,加锁,这是 RedisCache 中唯一一个同步方法

    @Override
    public synchronized <T> T get(Object key, Callable<T> valueLoader) {
       ValueWrapper result = get(key);
        
       if (result != null) {
          return (T) result.get();
       }
        
       T value = valueFromLoader(key, valueLoader);
       put(key, value);
       return value;
    }
    

    通过打断点的方式可以知道 RedisCache 默认调用的是 lookup(),因此不能应对缓存穿透,如果有相关需求,可以这样配置:@Cacheable(sync = true),开启同步模式,此配置只在 @Cacheable 中才有。

    总结

    Spring Cache 对于读模式下缓存失效的解决方案:

    • 缓存穿透:cache-null-values: true,允许写入空值
    • 缓存击穿:@Cacheable(sync = true),加锁
    • 缓存雪崩:time-to-live:xxx,设置不同的过期时间

    而对于写模式,Spring Cache 并没有相应处理,我们需要使用其它方式处理。


    总的来说:

    1、对于常规数据(读多写少,及时性、一致性要求不高的数据)完全可以使用 Spring Cache

    2、对于特殊数据(比如要求高一致性)则需要特殊处理

  • 相关阅读:
    对于JavaScript中this关键字的理解
    使用DOM对表格进行增删
    sql server 存储过程
    sql sever 基础 练习题
    sql sever 基础 建表
    第十章 嵌入式的Linux调试技术
    第九章 硬件抽象层 HAL
    第八章 蜂鸣器驱动
    LED:控制发光二极管
    第一个Linux驱动程序:统计单词个数
  • 原文地址:https://www.cnblogs.com/songjilong/p/12901397.html
Copyright © 2020-2023  润新知