Redis 序列化方式StringRedisSerializer、FastJsonRedisSerializer和KryoRedisSerializer

本文探讨了在Spring Data Redis中使用不同序列化方式的优缺点,包括JdkSerializationRedisSerializer、Jackson2JsonRedisSerializer、FastJsonRedisSerializer及KryoRedisSerializer。详细介绍了自定义序列化器的方法,以解决特定问题,如类型转换错误和反序列化失败。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://blog.youkuaiyun.com/xiaolyuh123/article/details/78682200

当我们的数据存储到Redis的时候,我们的键(key)和值(value)都是通过Spring提供的Serializer序列化到数据库的。RedisTemplate默认使用的是JdkSerializationRedisSerializer,StringRedisTemplate默认使用的是StringRedisSerializer。

Spring Data JPA为我们提供了下面的Serializer:GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。

序列化方式对比:

    JdkSerializationRedisSerializer: 使用JDK提供的序列化功能。 优点是反序列化时不需要提供类型信息(class),但缺点是需要实现Serializable接口,还有序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。
    Jackson2JsonRedisSerializer: 使用Jackson库将对象序列化为JSON字符串。优点是速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。 通过查看源代码,发现其只在反序列化过程中用到了类型信息。

问题描述

我们在用Spring data redis做redis缓存的时候,指定RedisTemplate的key和value序列化时遇到的问题。

    RedisTemplate的key指定成StringRedisSerializer序列化会报类型转换错误,如XXX类不能转换成String。
    使用Jackson2JsonRedisSerializer序列化的时候,如果实体类上没有set方法反序列化会报错。

问题分析

问题1:使用StringRedisSerializer做key的序列化时,StringRedisSerializer的泛型指定的是String,传其他对象就会报类型转换错误,在使用@Cacheable注解是key属性就只能传String进来。把这个序列化方式重写了,将泛型改成Object。源码:

    /**
     * 必须重写序列化器,否则@Cacheable注解的key会报类型转换错误
     *
     * @authors yuhao.wang
     */
    public class StringRedisSerializer implements RedisSerializer<Object> {
     
        private final Charset charset;
     
        private final String target = "\"";
     
        private final String replacement = "";
     
        public StringRedisSerializer() {
            this(Charset.forName("UTF8"));
        }
     
        public StringRedisSerializer(Charset charset) {
            Assert.notNull(charset, "Charset must not be null!");
            this.charset = charset;
        }
     
        @Override
        public String deserialize(byte[] bytes) {
            return (bytes == null ? null : new String(bytes, charset));
        }
     
        @Override
        public byte[] serialize(Object object) {
            String string = JSON.toJSONString(object);
            if (string == null) {
                return null;
            }
            string = string.replace(target, replacement);
            return string.getBytes(charset);
        }
    }

问题2:我们放弃用jackjson来做value的序列化,使用FastJson来做。重写一些序列化器,并实现RedisSerializer接口。源码如下:

    public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
     
        public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
     
        private Class<T> clazz;
     
        public FastJsonRedisSerializer(Class<T> clazz) {
            super();
            this.clazz = clazz;
        }
     
        @Override
        public byte[] serialize(T t) throws SerializationException {
            if (t == null) {
                return new byte[0];
            }
            return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
        }
     
        @Override
        public T deserialize(byte[] bytes) throws SerializationException {
            if (bytes == null || bytes.length <= 0) {
                return null;
            }
            String str = new String(bytes, DEFAULT_CHARSET);
            return (T) JSON.parseObject(str, clazz);
        }
     
    }

新加入一种序列化KryoRedisSerializer。速度很快,源码如下:

    import com.esotericsoftware.kryo.Kryo;
    import com.esotericsoftware.kryo.io.Input;
    import com.esotericsoftware.kryo.io.Output;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
     
    import java.io.ByteArrayOutputStream;
     
    /**
     * @param <T>
     * @author yuhao.wang
     */
    public class KryoRedisSerializer<T> implements RedisSerializer<T> {
        Logger logger = LoggerFactory.getLogger(KryoRedisSerializer.class);
     
        public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
     
        private static final ThreadLocal<Kryo> kryos = ThreadLocal.withInitial(Kryo::new);
     
        private Class<T> clazz;
     
        public KryoRedisSerializer(Class<T> clazz) {
            super();
            this.clazz = clazz;
        }
     
        @Override
        public byte[] serialize(T t) throws SerializationException {
            if (t == null) {
                return EMPTY_BYTE_ARRAY;
            }
     
            Kryo kryo = kryos.get();
            kryo.setReferences(false);
            kryo.register(clazz);
     
            try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
                 Output output = new Output(baos)) {
                kryo.writeClassAndObject(output, t);
                output.flush();
                return baos.toByteArray();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
     
            return EMPTY_BYTE_ARRAY;
        }
     
        @Override
        public T deserialize(byte[] bytes) throws SerializationException {
            if (bytes == null || bytes.length <= 0) {
                return null;
            }
     
            Kryo kryo = kryos.get();
            kryo.setReferences(false);
            kryo.register(clazz);
     
            try (Input input = new Input(bytes)) {
                return (T) kryo.readClassAndObject(input);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
     
            return null;
        }
     
    }

自定义序列化的使用

    /**
     * @author yuhao.wang
     */
    @Configuration
    public class RedisConfig {
     
        /**
         * 重写Redis序列化方式,使用Json方式:
         * 当我们的数据存储到Redis的时候,我们的键(key)和值(value)都是通过Spring提供的Serializer序列化到数据库的。RedisTemplate默认使用的是JdkSerializationRedisSerializer,StringRedisTemplate默认使用的是StringRedisSerializer。
         * Spring Data JPA为我们提供了下面的Serializer:
         * GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。
         * 在此我们将自己配置RedisTemplate并定义Serializer。
         *
         * @param redisConnectionFactory
         * @return
         */
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            
            FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
            // 全局开启AutoType,不建议使用
            // ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
            // 建议使用这种方式,小范围指定白名单
            ParserConfig.getGlobalInstance().addAccept("com.xiaolyuh.");
     
            // 设置值(value)的序列化采用FastJsonRedisSerializer。
            redisTemplate.setValueSerializer(fastJsonRedisSerializer);
            redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
            // 设置键(key)的序列化采用StringRedisSerializer。
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
     
            redisTemplate.afterPropertiesSet();
            return redisTemplate;
        }
     
    }

注意:fastjson在2017年3月爆出了在1.2.24以及之前版本存在远程代码执行高危安全漏洞。所以要使用ParserConfig.getGlobalInstance().addAccept("com.xiaolyuh.");指定序列化白名单。详情可以点击这里查看

原文:https://blog.youkuaiyun.com/xiaolyuh123/article/details/78682200
版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值