zoukankan      html  css  js  c++  java
  • Redis 序列化方式StringRedisSerializer、FastJsonRedisSerializer和KryoRedisSerializer

    当我们的数据存储到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序列化时遇到的问题。

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

    问题分析

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

    /**
     * 必须重写序列化器,否则@Cacheable注解的key会报类型转换错误
     *
     */
    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;
        }
    
    }

    自定义序列化的使用

    @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;
        }
    
    }
  • 相关阅读:
    The Python Standard Library
    Python 中的round函数
    Python文件类型
    Python中import的用法
    Python Symbols 各种符号
    python 一行写多个语句
    免费SSL证书(https网站)申请,便宜SSL https证书申请
    元宇宙游戏Axie龙头axs分析
    OLE DB provider "SQLNCLI10" for linked server "x.x.x.x" returned message "No transaction is active.".
    The operation could not be performed because OLE DB provider "SQLNCLI10" for linked server "xxx.xxx.xxx.xxx" was unable to begin a distributed transaction.
  • 原文地址:https://www.cnblogs.com/alter888/p/9144427.html
Copyright © 2011-2022 走看看