• Springboot+redis序列化方式到底怎么选+redis配置类+redis工具类


    SpringBoot+Redis存储时序列化怎么选择

    在刚开始学习Redis时,我们在使用SpringBoot+Redis配置value的序列化方式时应该都是选择的jackson的GenericJackson2JsonRedisSerializer或者是fastjsonGenericFastJsonRedisSerializer两种序列化器,而key一般就是StringRedisSerializer。本文将探索,如果将Redis的value序列化器也改为StringRedisSerializer效率会有什么变化呢?

    前言

    最近项目中遇到了一个问题:有一个部门表里面近3w条数据,需要将这些数据一次性全部查出,并且这些数据使用的频率中等偏上,而项目中有用到了mybatis-plus,mybatis-plus中开了一个配置log-impl: org.apache.ibatis.logging.stdout.StdOutImpl就是将数据的日志全给打印了,然后发现一个接口调用用了20s左右,接口内部实现不但是进行了部门表的全部数据查询,而且还进行了后续的很多业务逻辑处理,导致接口超时问题。

    后来在本地进行调试,将mybatis-plus的sql日志关闭后,接口时间不超过4s,这是最小改动的做法了,但是有个问题:项目已经上线了,并且管理员那边说只给部署项目,不能修改配置,只能下次上线时提供配置修改的文档,可现在生产上数据加载不出来(接口超时,wtf)。

    看了下上次同事写的sql,直接select *…,直接用解释器看了下,3w条数据没走索引,把*换了,搞个覆盖索引,结果0.5s都没超过。再想想mybatis-plus的sql日志问题,决定把查出来的数据给放入到redis中。

    那么就回到标题的问题,序列化方式改怎么选择呢?

    刚开始想的是用redis的list数据类型,但是发现这个类型好像不能一次性全部将数据放入,只能一条一条加到list中,我裂开,那这加到啥时候了。后来决定就用普通的string类型吧,然后考虑下序列化方式使用字符串还是json类型。最终选择了StringRedisSerializer序列化器

    比较

    先贴个Demo出来,后面咱们看调试结果。

    redis的配置类,这边是定义了两种template,一种是jackson序列化的,一种是string的

    @Configuration
    @EnableCaching
    public class RedisConfig extends CachingConfigurerSupport {
        @Bean(name = "redisTemplate")
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            //参照StringRedisTemplate内部实现指定序列化器
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            redisTemplate.setKeySerializer(keySerializer());
            redisTemplate.setHashKeySerializer(keySerializer());
            redisTemplate.setValueSerializer(valueSerializer());
            redisTemplate.setHashValueSerializer(valueSerializer());
            return redisTemplate;
        }
    
        @Bean(name = "redisTemplate2")
        public RedisTemplate<String, Object> redisTemplate2(RedisConnectionFactory redisConnectionFactory){
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            //参照StringRedisTemplate内部实现指定序列化器
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            redisTemplate.setKeySerializer(keySerializer());
            redisTemplate.setHashKeySerializer(keySerializer());
            redisTemplate.setValueSerializer(keySerializer());
            redisTemplate.setHashValueSerializer(keySerializer());
            return redisTemplate;
        }
    
        private RedisSerializer<String> keySerializer(){
            return new StringRedisSerializer();
        }
    
        //使用Jackson序列化器
        private RedisSerializer<Object> valueSerializer(){
            return new GenericFastJsonRedisSerializer();
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    接口,后面的业务层就不贴了,具体实现都写在controller里了。

    @Resource(name = "redisTemplate")
    private RedisTemplate<String, Object> redisTemplate;
    @Resource(name = "redisTemplate2")
    private RedisTemplate<String, Object> redisTemplate2;
    
    @GetMapping("/addRedis1")
    public Student addRedis1() {
        StopWatch sw = new StopWatch();
        sw.start();
        List<Department> departments = studentService.listDept();
        redisTemplate.opsForValue().set("depts1", departments, 5, TimeUnit.MINUTES);
        sw.stop();
        System.out.println("数据量:"+ departments.size() + "Jackson序列总计耗时" + sw.getTotalTimeMillis());
        return null;
    }
    
    @GetMapping("/addRedis2")
    public Student addRedis2() {
        StopWatch sw = new StopWatch();
        sw.start();
        List<Department> departments = studentService.listDept();
        redisTemplate2.opsForValue().set("depts2", JSON.toJSONString(departments), 5, TimeUnit.MINUTES);
        sw.stop();
        System.out.println("数据量:"+ departments.size() + "String序列总计耗时" + sw.getTotalTimeMillis());
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    事不宜迟,抓紧进行测试吧。每个接口调用10次,咱们看表说话。

    jackson的序列化十次放入redis

    在这里插入图片描述

    string序列化十次放入redis

    在这里插入图片描述

    看表

    第一次第二次第三次第四次第五次第六次第七次第八次第九次第十次平均
    Jackson77052889569525365526248235295348546722884346.5
    string11231116107111601167121246491163115545391835.5

    这差距,离谱了。string序列化省2倍多时间,并且稳定性比jackson也更好一点。

    读取数据代码,这边都是用的json的parseArray来解析json字符串。

    @GetMapping("/getRedis1")
    public Student getRedis1() {
        StopWatch sw = new StopWatch();
        sw.start();
        List<Department> o = JSON.parseArray(redisTemplate2.opsForValue().get("depts1").toString(), Department.class);
        sw.stop();
        System.out.println("数据量:"+ o.size() + "Jackson序列总计耗时" + sw.getTotalTimeMillis());
        return null;
    }
    
    @GetMapping("/getRedis2")
    public Student getRedis2() {
        StopWatch sw = new StopWatch();
        sw.start();
        List<Department> o = JSON.parseArray(redisTemplate2.opsForValue().get("depts2").toString(), Department.class);
        sw.stop();
        System.out.println("数据量:"+ o.size() + "String序列总计耗时" + sw.getTotalTimeMillis());
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这两种序列化器其实并不局限于某一种,可以根据数据的实际情况进行选择,甚至可以设置多个template来进行使用。需要知道:redis中单个key和value的最大值都是512m,如果数据量过多的情况下请谨慎选择,如上例:或许可以将3w左右数据的给拆分为几个集合,然后分别存到几个key中

    配置类、工具类

    最后提供一个redis的配置类和工具类,方便后续使用时及时查找。

    配置类

    @Configuration
    @EnableCaching
    public class RedisConfig {
        @Autowired
        private RedisConnectionFactory factory;
    
        @Bean
        public RedisTemplate<String, Object> redisTemplate() {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            // 序列化方式自行选择,String或是Jackson,FastJson也可以
            redisTemplate.setHashValueSerializer(new StringRedisSerializer());
            redisTemplate.setValueSerializer(new StringRedisSerializer());
            redisTemplate.setConnectionFactory(factory);
            return redisTemplate;
        }
    
        @Bean
        public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
            return redisTemplate.opsForValue();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    redis工具类,此工具类中获取之后用到了Gson工具来进行解析数据,也可以自行更换为其他的如fastjson等。

    @Component
    public class RedisUtils {
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        @Autowired
        private ValueOperations<String, String> valueOperations;
        /**
         * 默认过期时长,单位:秒
         */
        public final static long DEFAULT_EXPIRE = 60 * 60 * 24;
        /**
         * 默认过期时长,单位:秒
         */
        public final static long DEFAULT_URL_EXPIRE = 60;
        /**
         * 五分钟
         */
        public final static long FIVE_MIN = 60 * 5;
        /**
         * 不设置过期时长
         */
        public final static long NOT_EXPIRE = -1;
        private final static Gson gson = new Gson();
    
        public void set(String key, Object value, long expire) {
            valueOperations.set(key, toJson(value));
            if (expire != NOT_EXPIRE) {
                redisTemplate.expire(key, expire, TimeUnit.SECONDS);
            }
        }
    
        public void set(String key, Object value) {
            set(key, value, DEFAULT_EXPIRE);
        }
    
        public void setT(String key, Object value) {
            set(key, value, DEFAULT_URL_EXPIRE);
        }
    
        public <T> T get(String key, Class<T> clazz, long expire) {
            String value = valueOperations.get(key);
            if (expire != NOT_EXPIRE) {
                redisTemplate.expire(key, expire, TimeUnit.SECONDS);
            }
            return value == null ? null : fromJson(value, clazz);
        }
    
        public <T> T get(String key, Class<T> clazz) {
            return get(key, clazz, NOT_EXPIRE);
        }
    
        public String get(String key, long expire) {
            String value = valueOperations.get(key);
            if (expire != NOT_EXPIRE) {
                redisTemplate.expire(key, expire, TimeUnit.SECONDS);
            }
            return value;
        }
    
        public String get(String key) {
            return get(key, NOT_EXPIRE);
        }
    
        public String getT(String key) {
            return get(key, DEFAULT_URL_EXPIRE);
        }
    
        public void delete(String key) {
            redisTemplate.delete(key);
        }
    
        /**
         * Object转成JSON数据
         */
        private String toJson(Object object) {
            if (object instanceof Integer || object instanceof Long || object instanceof Float ||
                    object instanceof Double || object instanceof Boolean || object instanceof String) {
                return String.valueOf(object);
            }
            return gson.toJson(object);
        }
    
        /**
         * JSON数据,转成Object
         */
        private <T> T fromJson(String json, Class<T> clazz) {
            return gson.fromJson(json, clazz);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    任何技术选型都是需要根据项目的实际情况不断尝试和探索,不可能一步到位。警醒自己:使用任何技术前考虑实际情况!!!

  • 相关阅读:
    git 知:提交格式
    网络安全内网渗透之信息收集--systeminfo查看电脑有无加域
    计算机基础 CMOS
    【C/C++】malloc/free 和 new/delete
    取得网络中SQL的服务器列表
    ScanNet数据集转rosbag的脚本
    docker 增加cpu线程数
    vue-router的基本用法
    【Leetcode每日一题】 动态规划 - 简单多状态 dp 问题 - 删除并获得点数(难度⭐⭐)(76)
    场景分类任务可用数据集(部分)
  • 原文地址:https://blog.csdn.net/weixin_45248492/article/details/126722361