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

发布于:2022-12-27 ⋅ 阅读:(532) ⋅ 点赞:(0)

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

在刚开始学习Redis时,我们在使用SpringBoot+Redis配置value的序列化方式时应该都是选择的jackson的GenericJackson2JsonRedisSerializer或者是fastjson的GenericFastJsonRedisSerializer两种序列化器,而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();
    }
    
}

接口,后面的业务层就不贴了,具体实现都写在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;
}

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

jackson的序列化十次放入redis

在这里插入图片描述

string序列化十次放入redis

在这里插入图片描述

看表

第一次 第二次 第三次 第四次 第五次 第六次 第七次 第八次 第九次 第十次 平均
Jackson 7705 2889 5695 2536 5526 2482 3529 5348 5467 2288 4346.5
string 1123 1116 1071 1160 1167 1212 4649 1163 1155 4539 1835.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;
}

这两种序列化器其实并不局限于某一种,可以根据数据的实际情况进行选择,甚至可以设置多个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();
    }

}

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);
    }
}

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

本文含有隐藏内容,请 开通VIP 后查看