zoukankan      html  css  js  c++  java
  • springboot整合redis缓存一些知识点

    前言

    最近在做智能家居平台,考虑到家居的控制需要快速的响应于是打算使用redis缓存。一方面减少数据库压力另一方面又能提高响应速度。项目中使用的技术栈基本上都是大家熟悉的springboot全家桶,在springboot2.x以后操作redis的客户端推荐使用lettuce(生菜)取代jedis。

    jedis的劣势主要在于直连redis,又无法做到弹性收缩。

    一、配置文件

    application.yml文件中的内容

    spring:
      application:
        name: simple-lettuce
      cache:
        type: redis
        redis:
          # 缓存超时时间ms
          time-to-live: 60000
          # 是否缓存空值
          cache-null-values: true
      redis:
        host: 127.0.0.1
        port: 6379
        password: 123456
        # 连接超时时间(毫秒)
        timeout: 60000
        # Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
        database: 1
        # spring2.x redis client 采用了lettuce(生菜),放弃使用jedis
        lettuce:
          # 关闭超时时间
          shutdown-timeout: 30000
          pool:
            # 连接池最大连接数(使用负值表示没有限制) 默认 8
            max-active: 30
            # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
            max-wait: -1
            # 连接池中的最大空闲连接 默认 8
            max-idle: 8
            # 连接池中的最小空闲连接 默认 0
            min-idle: 0

    说明:

    • spring.cache.type: redis

    已经表明使用项目采用redis做为缓存方式。

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

    表示是否缓存空值,一般情况下是允许的。因为这涉及到缓存的三大问题:缓存穿透、缓存雪崩、缓存击穿。

    如果设置false即不允许缓存空值,这样会导致很多请求数据库没有的数据时,不会缓存到redis导致每次都会请求到数据库。这种情况即:缓存穿透。

    具体想初步了解这些概念可以参考文章:缓存三大问题及解决方案!

    二、config配置类

    
    @Configuration
    @EnableCaching
    public class RedisTemplateConfig extends CachingConfigurerSupport {
    
        private static Map<String, RedisCacheConfiguration> cacheMap = Maps.newHashMap();
    
        @Bean(name = "stringRedisTemplate")
        @ConditionalOnMissingBean(name = "stringRedisTemplate") //表示:如果容器已经有redisTemplate bean就不再注入
        public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {return new StringRedisTemplate(redisConnectionFactory);
        }
    
        @Bean(name = "redisTemplate")
        @ConditionalOnMissingBean(name = "redisTemplate")
        public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
            System.out.println("RedisTemplateConfig.RedisTemplate");
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            // key的序列化采用StringRedisSerializer
            template.setKeySerializer(keySerializer());
            template.setHashKeySerializer(keySerializer());
            // value值的序列化采用fastJsonRedisSerializer
            template.setValueSerializer(valueSerializer()); //使用fastjson序列化
            template.setHashValueSerializer(valueSerializer()); //使用fastjson序列化
            template.setConnectionFactory(lettuceConnectionFactory);
            return template;
        }
    
        /**
         * 添加自定义缓存异常处理
         * 当缓存读写异常时,忽略异常
         * 参考:https://blog.csdn.net/sz85850597/article/details/89301331
         */
        @Override
        public CacheErrorHandler errorHandler() {
            return new IgnoreCacheErrorHandler();
        }
    
        @SuppressWarnings("Duplicates")
        @Bean
        @Primary//当有多个管理器的时候,必须使用该注解在一个管理器上注释:表示该管理器为默认的管理器
        public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
            // 默认配置
            RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                    .serializeKeysWith(keyPair())
                    .serializeValuesWith(valuePair())
                    .entryTtl(Duration.ofSeconds(DEFAULT_TTL_SECS)) //设置过期时间
                    .disableCachingNullValues();
    
            // 其它配置
            for(MyCaches cache : MyCaches.values()) {
                cacheMap.put(cache.name(),
                        RedisCacheConfiguration.defaultCacheConfig()
                                .serializeKeysWith(keyPair())
                                .serializeValuesWith(valuePair())
                                .entryTtl(cache.getTtl())
                                // .disableCachingNullValues() // 表示不允许缓存空值
                                .disableKeyPrefix() // 不使用默认前缀
                        // .prefixKeysWith("mytest") // 添加自定义前缀
                );
            }
    
            /** 遍历MyCaches添加缓存配置*/
            RedisCacheManager cacheManager = RedisCacheManager.builder(
                    RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory)
            )
                    .cacheDefaults(defaultCacheConfig)
                    .withInitialCacheConfigurations(cacheMap)
                    .transactionAware()
                    .build();
    
            ParserConfig.getGlobalInstance().addAccept("mypackage.db.entity.");
            return cacheManager;
        }
    
        /**
         * key序列化方式
         * @return
         */
        private RedisSerializationContext.SerializationPair<String> keyPair() {
            RedisSerializationContext.SerializationPair<String> keyPair =
                    RedisSerializationContext.SerializationPair.fromSerializer(keySerializer());
            return keyPair;
        }
    
        private RedisSerializer<String> keySerializer() {
            return new StringRedisSerializer();
        }
    
        /**
         * value序列化方式
         * @return
         */
        private RedisSerializationContext.SerializationPair<Object> valuePair() {
            RedisSerializationContext.SerializationPair<Object> valuePair =
                    RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer());
            return valuePair;
        }
    
        /**
         * 使用fastjson序列化
         * @return
         */
        private RedisSerializer<Object> valueSerializer() {
            MyFastJsonRedisSerializer<Object> fastJsonRedisSerializer = new MyFastJsonRedisSerializer<>(Object.class);
            return fastJsonRedisSerializer;
        }
    
        @Getter
        private enum MyCaches {
            defaultCache(Duration.ofDays(1)),
            MyCaches(Duration.ofMinutes(10));
    
            MyCaches(Duration ttl) {
                this.ttl = ttl;
            }
            /** 失效时间 */
            private Duration ttl = Duration.ofHours(1);
        }
    }

     说明

    1. 类上的注解@EnableCaching

    表明开启缓存功能。

    2. extends CachingConfigurerSupport

    这个类就很丰富了,其实如果没有什么特别操作也可以不用继承这个类。

    这个类可以支持动态选择缓存方式,比如项目中不止一种缓存方案,有可能有ehcache那么可以自定义在什么情况下使用redis使用情况下使用ehcache。还有一些有关异常的处理。我也不是很懂具体可以参考:

    3. StringRedisTemplate和RedisTemplate的使用

    (1)两者的主要差别是:如果你只想缓存简单的字符串选择StringRedisTemplate是一个明智的举措。如果想使用redis缓存一些对象数据肯定是要选择RedisTemplate。
     
    (2)RedisTemplate需要注意一点就是要怎么选择序列化工具。默认使用jdk的序列化缓存数据后即value值是无法直接阅读的而存的二进制数据。
    通常我们会选择jackson或者fastjson来序列化对象,把对象转换成json格式。两者序列化对象后都会在头部加上一个对象类路径如:@type com.mypackage.entity.User。这个也算是一种安全策略。
    比如使用fastjosn就会在cacheManager中指定序列化对象的包所在位置白名单:ParserConfig.getGlobalInstance().addAccept("mypackage.db.entity.");
    fastjson官方说明:https://github.com/alibaba/fastjson/wiki/enable_autotype
     
    (3)还有需要注意如果value是string类型。RedisTemplate会在字符串外围再加一对双引号,如""abc""。如果使用StringRedisTemplate读取则能得到abc,但是我在项目使用Jedis读取就成了"abc"这就导致这些字符串无法被反序列化。
     
    (4)StringRedisTemplate和RedisTemplate两者数据是相互隔离的,如果使用StringRedisTemplate存入的数据使用RedisTemplate是无法读取、删除的。
     
     

    三、缓存注解使用

    @Cacheable 使用在查询方法上

    @CachePut 使用在更新、保存方法上

    @CacheEvict 使用在删除方法上

    需要注意的是@Cacheable@CachePut方法一定要有返回被缓存对象。因为注解使用的AOP切面如果没有返回值表示缓存对象为空值。

    @CacheConfig注解在类上,可以选择使用哪个缓存、缓存管理器、Key生成器

    好了以上就是最近在项目中的一些知识点总结,如果以后使用缓存有新的体会我会同步更新的。

  • 相关阅读:
    Ubuntu完全教程,让你成为Ubuntu高手!
    Centos7安装完毕后重启提示Initial setup of CentOS Linux 7 (core)的解决方法
    MS SQL操作Xml示例
    MY SQL sql_mode设置
    MS SQL " 无法启动分布式事务"问题的解决思路
    MS SQL常用系统表汇总
    SQL不同服务器数据库之间数据操作整理
    OPENQUERY用法
    SQL Compare 10.4.8.87 Edition 数据库比较工具 完全破解+使用教程
    各种主流 SQLServer 迁移到 MySQL 工具对比
  • 原文地址:https://www.cnblogs.com/backnow/p/11824061.html
Copyright © 2011-2022 走看看