1. 程式人生 > >Redis 序列化方式StringRedisSerializer、FastJsonRedisSerializer和KryoRedisSerializer

Redis 序列化方式StringRedisSerializer、FastJsonRedisSerializer和KryoRedisSerializer

out public ack 我們 must instance key) strings initial

當我們的數據存儲到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;
    }

}

Redis 序列化方式StringRedisSerializer、FastJsonRedisSerializer和KryoRedisSerializer