Redis序列化引发的惨案

由于Redis是基于内存操作的数据库,所以速度非常的快,现在我们的项目中或多或少都会用到Redis,今天跟大家分享一个由Redis序列化问题导致的事故,希望大家引以为戒,不要犯我一样的错误。

我们你项目中,使用的是springboot集成的RedisTemplate,用过的同学可能都知道,RedisTemplate在帮我们将数据存到Redis的时候,都会将我们的数据进行序列化,不管是key还是value,都会进行序列化,默认key和value使用的都是JdkSerializationRedisSerializer序列化工具,使用默认的话,我们在命令行看的话,只能看到这样的:

感觉不是很友好,所以我们之前将value的序列化工具改成了Jackson2JsonRedisSerializer,将key的序列化工具改成了StringRedisSerializer,至于各种工具的对比,可以看这张图:

下面是我们RedisConfig的配置:

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 默认Redis缓存过期时间为1小时50分钟
        RedisCacheConfiguration redisCacheConfiguration =
                RedisCacheConfiguration.defaultCacheConfig()
                        .entryTtl(Duration.ofMinutes(110));
        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(factory);
        return stringRedisTemplate;
    }
}

下面就到了我们入坑的时候了。。。

之前我们使用Redistemplate的时候都是这样注入的:

@Autowired

private RedisTemplate redisTemplate;

因为最后我们取的时候要的是string类型的值,所以要强转一下,有一天根据idea的提示,就改成了下面这样:

@Autowired

private RedisTemplate<String,String> redisTemplate;

就不需要强转了,本地测试set、get都没有问题,测试环境测下来也是OK的,然后sprint go/nogo 会议上决定上线,发布很顺利,悲剧的是,刚上线,就有用户报无法操作。

看问题的时候小手都是抖的,这次上线的功能还是比较多的。。还好从日志中发现了问题所在,其中有一段逻辑是,根据Redis里的值跟用户传来的值进行比较,看日志,它们的值是相等的,但是它就是没有走相等的逻辑?当时我就蒙了,什么鬼?

然后就对比了一下,发布前和发布后的日志,发现,打印出来的Redis里的值在发布前后是不一样的,发布前值是 "\"value\"",发布后是"value",一眼就看出来是序列化的问题,找到问题就不慌了,虽然这时候我已经忘了我之前的 "优化" 了,于是去看代码,发现相关代码改动的就这有上面提到的这一块了,为什么呢?

因为,我们之前的那种注入方式,注入的是RedisConfig里的RedisTemplate,但是改完注入的是RedisConfig里的StringRedisTemplate,之前的value使用的是Jackson2JsonRedisSerializer序列化工具,我们生产上Redis里面的数据也是用Jackson2JsonRedisSerializer序列化之后存的,是这种格式的:"\"value\"",如果你get的时候还用的是Jackson2JsonRedisSerializer,那么它会给你反序列化成 "value" ,但是我们发布后,因为注入的是StringRedisTemplate,它默认的value序列化工具是StringRedisSerializer,在我们get的时候,它只能将"\"value\"" 序列化成"\"value\"",然后比较的时候,字符串 "\"value\"" 和 "value"当然是不相同了。

问题找到了,那么怎么解决呢?办法有两种

  • 将Redis里面相关的数据清除掉,这样我们set和get都是用的是同一个序列化工具,就不会存在上面的问题了,但是我们发布是不停栈的,当时是下午两点多,正式用户使用的高峰期,这个操作肯定是不可取的。
  • 将注入的RedisTemplate改成之前的,或者将现在的StringRedisTemplate的value序列化工具换成Jackson2JsonRedisSerializer。

最后我们的选择是,执行第二种方案,将注入的RedisTemplate改成之前的,然后决定将下次发布的时间放到晚上用户不操作的时候,RedisTemplate改回来,再执行第一种方案。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 点我我会动 设计师:上身试试 返回首页