转载

Spring Boot Redis 序列化方案的选择

Spring Boot Data Redis 给我们提供了即插即用的体验,大部分默认配置已经满足了我们的需求,而其中序列化方案选择的是原生的 JdkSerializationRedisSerializer

RedisTemplate.java

if (defaultSerializer == null) {

	defaultSerializer = new JdkSerializationRedisSerializer(
			classLoader != null ? classLoader : this.getClass().getClassLoader());
}

复制代码

当然,我们也可以选择 Spring Boot Data Redis 的其他序列化方案进行配置。

RedisSerializer 的实现

Spring Boot Redis 序列化方案的选择

在此基础上,我们可以自定义我们自己的序列化方案。

自定义JSON序列化方案

FastJsonRedisSerializer.java

public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    private FastJsonConfig fastJsonConfig = new FastJsonConfig();
    private Class<T> type;

    public FastJsonRedisSerializer(Class<T> type) {
        this.type = type;
    }

    public FastJsonConfig getFastJsonConfig() {
        return fastJsonConfig;
    }

    public void setFastJsonConfig(FastJsonConfig fastJsonConfig) {
        this.fastJsonConfig = fastJsonConfig;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        try {
            return JSON.toJSONBytes(
                    fastJsonConfig.getCharset(),
                    t,
                    fastJsonConfig.getSerializeConfig(),
                    fastJsonConfig.getSerializeFilters(),
                    fastJsonConfig.getDateFormat(),
                    JSON.DEFAULT_GENERATE_FEATURE,
                    fastJsonConfig.getSerializerFeatures()
            );
        } catch (Exception ex) {
            throw new SerializationException("Could not serialize: " + ex.getMessage(), ex);
        }
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        try {
            return (T) JSON.parseObject(
                    bytes,
                    fastJsonConfig.getCharset(),
                    type,
                    fastJsonConfig.getParserConfig(),
                    fastJsonConfig.getParseProcess(),
                    JSON.DEFAULT_PARSER_FEATURE,
                    fastJsonConfig.getFeatures()
            );
        } catch (Exception ex) {
            throw new SerializationException("Could not deserialize: " + ex.getMessage(), ex);
        }
    }
}
复制代码

当然,这个是基于 fastjson 的序列化方案,不仅提供了相比于JDK序列化更小的体积,序列化和反序列化的速度上也更快。

FST和Kryo序列化方案

这里就粘贴相关代码了,详情可见 redis-serializer-line

性能对比(基准)

JDKFastJsonFSTKryo 测试结果如下,测试项目可见 redis-serializer-line

原生JDK序列化方案[序列化100000次]耗时:2160 ms, 大小 44000000
原生JDK序列化方案[序列化100000次]耗时:1406 ms, 大小 44000000
FastJson序列化方案[序列化100000次]耗时:679 ms, 大小 18800000
FastJson序列化方案[序列化100000次]耗时:289 ms, 大小 18800000
FST序列化方案[序列化100000次]耗时:273 ms, 大小 10400000
FST序列化方案[序列化100000次]耗时:130 ms, 大小 10400000
Kryo序列化方案[序列化100000次]耗时:498 ms, 大小 14000000
Kryo序列化方案[序列化100000次]耗时:215 ms, 大小 14000000
复制代码

总结

FSTKryo 提供了更小的体积和更快的序列化速度,比 Fastjson 更有性能优势。但是需要提前将需要序列化的对象进行 register ,这增加了编码难度。而 Kryo 线程不安全,更需要进行处理,比如通过 KryoPool 进行池化处理。

通过更换序列化方案,可以解决 Redis IO 压力过大的问题,提升性能。

外话

Dubbo 的项目中提供了大量的序列化方案,在IO传输中体积小,速度快,所以在微服务领域比 Spring Cloud 更具有性能优势。我们在实现序列化是可以参考以下 Dubbo 的源码进行便携,毕竟千锤百炼的代码很有借鉴价值。

比如, DubboFST 的创建时,会对需要序列化的对象进行 registerClass , 这会显著的增强性能。而在使用Kryo时,不仅 register 序列化的对象,还需要针对基本类型进行 register

FST 本身已经对基本类型进行注册了,所以 FST 在易用性上比 Kryo 更有优势。

欢迎关注我呀,我会不定期更新开发中的小技巧,小手段。Git地址: gitee.com/SoftMeng/sp…

FstFactory.java

public FstFactory() {
        SerializableClassRegistry.getRegisteredClasses().keySet().forEach(conf::registerClass);
    }
复制代码

Kryo.java

public Kryo create() {
        if (!kryoCreated) {
            kryoCreated = true;
        }

        Kryo kryo = new CompatibleKryo();

        // TODO
//        kryo.setReferences(false);
        kryo.setRegistrationRequired(registrationRequired);

        kryo.addDefaultSerializer(Throwable.class, new JavaSerializer());
        kryo.register(Arrays.asList("").getClass(), new ArraysAsListSerializer());
        kryo.register(GregorianCalendar.class, new GregorianCalendarSerializer());
        kryo.register(InvocationHandler.class, new JdkProxySerializer());
        kryo.register(BigDecimal.class, new DefaultSerializers.BigDecimalSerializer());
        kryo.register(BigInteger.class, new DefaultSerializers.BigIntegerSerializer());
        kryo.register(Pattern.class, new RegexSerializer());
        kryo.register(BitSet.class, new BitSetSerializer());
        kryo.register(URI.class, new URISerializer());
        kryo.register(UUID.class, new UUIDSerializer());
        UnmodifiableCollectionsSerializer.registerSerializers(kryo);
        SynchronizedCollectionsSerializer.registerSerializers(kryo);

        // now just added some very common classes
        // TODO optimization
        kryo.register(HashMap.class);
        kryo.register(ArrayList.class);
        kryo.register(LinkedList.class);
        kryo.register(HashSet.class);
        kryo.register(TreeSet.class);
        kryo.register(Hashtable.class);
        kryo.register(Date.class);
        kryo.register(Calendar.class);
        kryo.register(ConcurrentHashMap.class);
        kryo.register(SimpleDateFormat.class);
        kryo.register(GregorianCalendar.class);
        kryo.register(Vector.class);
        kryo.register(BitSet.class);
        kryo.register(StringBuffer.class);
        kryo.register(StringBuilder.class);
        kryo.register(Object.class);
        kryo.register(Object[].class);
        kryo.register(String[].class);
        kryo.register(byte[].class);
        kryo.register(char[].class);
        kryo.register(int[].class);
        kryo.register(float[].class);
        kryo.register(double[].class);

        for (Class clazz : registrations) {
            kryo.register(clazz);
        }

        SerializableClassRegistry.getRegisteredClasses().forEach((clazz, ser) -> {
            if (ser == null) {
                kryo.register(clazz);
            } else {
                kryo.register(clazz, (Serializer) ser);
            }
        });

        return kryo;
    }
复制代码
原文  https://juejin.im/post/5d5e10d2e51d4561b416d487
正文到此结束
Loading...