Spring Boot Redis 序列化方案的选择

作者:小哥他三叔

juejin.im/post/5d5e10d2e51d4561b416d487

Redis的使用越来越广泛,当遇见性能瓶颈时,我们应该如何去解决呢?

Redis序列化方案

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的实现

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

自定义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序列化方案

这里就粘贴相关代码了,详情可见

https://gitee.com/SoftMeng/spring-boot-skill/tree/master/redis-serializer-line

性能对比(基准)

JDK、FastJson、FST、Kryo测试结果如下,测试项目可见

https://gitee.com/SoftMeng/spring-boot-skill/tree/master/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

总结

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

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

外话

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

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

FST 本身已经对基本类型进行注册了,所以FST在易用性上比Kryo更有优势,也提供了@Version对POJO的新增字段进行版本管理。

Dubbo中的序列化举例

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;
    }
精彩推荐
一百期Java面试题汇总SpringBoot内容聚合IntelliJ IDEA内容聚合Mybatis内容聚合

欢迎长按下图关注公众号后端技术精选

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值