• 在Dubbo中使用Kryo序列化协议


    Kryo是什么?

    Kryo是用于Java的快速高效的二进制对象图序列化框架。

    该项目的目标是高速,小尺寸和易于使用的API。不管是将对象持久保存到文件、数据库还是通过网络传输时,都可以尝试Kryo。

    Kryo还可以执行自动的深浅复制/克隆。这是从对象到对象的直接复制,而不是从对象到字节的复制。

    具体可以参考Kryo官网

    在Dubbo中使用Kryo

    本文基于Dubbo版本2.7.8

    Dubbo支持非常多的序列化方式,如hession2avroFST等等,其中Dubbo官网推荐的序列化方式是Kryo,因为Kryo是一种非常成熟的序列化实现,已经在Twitter、Groupon、Yahoo以及多个著名开源项目(如Hive、Storm)中广泛的使用。

    开始

    在Dubbo中使用Kryo非常方便,首先引入依赖

    // 解决一些Kryo特殊序列化,https://github.com/magro/kryo-serializers
    implementation  'de.javakaffee:kryo-serializers:0.43'
    // 高性能序列化框架, https://github.com/EsotericSoftware/kryo
    implementation 'com.esotericsoftware:kryo:4.0.2'
    

    如果只是简单使用,引入kryo即可,如果要支持一些例如List接口,则需要引入kryo-serializers,它针对一些特殊类为Kryo做了适配。

    配置

    在Dubbo中启用Kryo序列化方式,这里使用SpringBoot YML配置方式

    protocol:
        serialization: kryo
        optimizer: org.hmwebframework.microservice.dubbo.serialize.SerializationOptimizerImpl
    

    其中org.hmwebframework.microservice.dubbo.serialize.SerializationOptimizerImpl是指定Kryo序列化类,例如

    public class SerializationOptimizerImpl implements SerializationOptimizer {
        public Collection<Class> getSerializableClasses() {
            List<Class> classes = new LinkedList<Class>();
            classes.add(BidRequest.class);
            classes.add(BidResponse.class);
            classes.add(Device.class);
            classes.add(Geo.class);
            classes.add(Impression.class);
            classes.add(SeatBid.class);
            return classes;
        }
    }
    

    到这,Dubbo使用Kryo就已经OK了。

    为什么要定义SerializationOptimizer实现类?

    首先我们分析下SerializationOptimizer

    public interface SerializationOptimizer {
    
        /**
         * Get serializable classes
         *
         * @return serializable classes
         * */
        Collection<Class<?>> getSerializableClasses();
    }
    

    提供了一个接口方法,用于获取序列化的java类型列表,在DubboProtocol#optimizeSerialization中被使用

        private void optimizeSerialization(URL url) throws RpcException {
            // ...
            try {
                Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
                // 判断是否为SerializationOptimizer实现类
                if (!SerializationOptimizer.class.isAssignableFrom(clazz)) {
                    throw new RpcException("The serialization optimizer " + className + " isn't an instance of " + SerializationOptimizer.class.getName());
                }
    
                SerializationOptimizer optimizer = (SerializationOptimizer) clazz.newInstance();
    
                if (optimizer.getSerializableClasses() == null) {
                    return;
                }
                // 将SerializationOptimizer中定义的类型列表,注册到SerializableClassRegistry
                for (Class c : optimizer.getSerializableClasses()) {
                    SerializableClassRegistry.registerClass(c);
                }
    
                optimizers.add(className);
    
            } catch (ClassNotFoundException e) {
                // ...
            }
        }
    

    接着,从SerializableClassRegistry中拿出注册的类型,进行Kryo的类型注册,可以看到SerializableClassRegistry#getRegisteredClasses被FST和Kryo使用,证明FST和Kryo都需要进行序列化类的注册,当然FST也支持不注册序列化类型。

    Kryo类注册的具体细节,AbstractKryoFactory#create

    // ...
    for (Class clazz : registrations) {
        kryo.register(clazz);
    }
    // 遍历取出SerializableClassRegistry的注册类,依次将类注册到Kryo
    SerializableClassRegistry.getRegisteredClasses().forEach((clazz, ser) -> {
        if (ser == null) {
            kryo.register(clazz);
        } else {
            kryo.register(clazz, (Serializer) ser);
        }
    });
    

    循环取出SerializableClassRegistry中的注册类进行注册,看到这里也能明白,为什么Dubbo官网的SerializationOptimizer例子需要使用LinkedList。

    为什么Kryo需要进行类的注册,且保持顺序?

    类的注册

    在Dubbo这样的RPC框架进行通信时,性能瓶颈往往在于RPC传输过程中的网络IO耗时,提升网络IO的办法,一是加大带宽,二是减小传输的字节数,而高性能序列化框架可以做到的就是减小传输的字节数。

    Kryo注册类的时候,使用了一个int类型的ID来与类进行关联,在序列化该类的实例时,用int ID来标识类型,反序列化该类时,同样通过int ID来找到类型,这比写出类名高效的多。

    维持类注册顺序

    Kryo注册类的时候,可以指定类关联的int ID,例如

    Kryo kryo = new Kryo();
    kryo.register(SomeClass.class, 10);
    kryo.register(AnotherClass.class, 11);
    kryo.register(YetAnotherClass.class, 12);
    

    但是上面我们讲到,Dubbo对Kryo做了相当程度的集成,导致我们没法给类指定int ID,但是我们可以保证服务提供方和消费方类注册顺序的一致,间接地保证了int ID的一致性。

    优化

    反射获取待注册的类

    在Dubbo中使用Kryo时,我们需要实现一个SerializationOptimizer,并提供一个注册类列表。随着项目规模扩大,不可能时时刻刻想着维护这个注册类列表,所以我们可以使用反射来自动获取这个注册类列表

    引入依赖

    // Java反射工具包
    implementation 'org.reflections:reflections:0.9.11'
    

    编写接口,

    public interface KryoDubboSerializable
            extends Serializable {
    }
    

    编写SerializationOptimizer实现类

    @Slf4j
    public abstract class AbstractSerializationOptimizerImpl
            implements SerializationOptimizer {
        private final List<Class<?>> classList;
    
        public AbstractSerializationOptimizerImpl() {
            var reflections = new Reflections(
                    new ConfigurationBuilder()
                            .forPackages(basePackage())
                            .addScanners(new SubTypesScanner())
            );
            this.classList = reflections.getSubTypesOf(KryoDubboSerializable.class)
                    .stream()
                	// Kryo序列化协议要求类注册顺序一致
                    .sorted(Comparator.comparing(Class::getSimpleName))
                    .collect(Collectors.toList());
            log.info("load {} classes to use kryo serializable", this.classList.size());
            log.debug("kryo serializable classes: {}", this.classList.stream().map(Class::getSimpleName).collect(Collectors.joining(",")));
        }
    
        @Override
        public Collection<Class<?>> getSerializableClasses() {
            return classList;
        }
    
        /**
         * 扫描包路径
         *
         * @return packages
         */
        protected abstract String[] basePackage();
    
    }
    

    每次使用时,只需要继承AbstractSerializationOptimizerImpl,并提供待注册包路径(支持多个),待注册的类需要实现KryoDubboSerializable接口,这是为了在一定程度上提升灵活性(如果不需要注册到Kryo,不实现该接口即可)。

    参考

  • 相关阅读:
    initramfs扫描磁盘前改变磁盘上电顺序
    “井号键”用英语怎么说?
    syslog,rsyslog and syslog-ng
    glob (programming) and spool (/var/spool)
    CentOS 6.5语言包裁剪
    C​P​U​_​C​S​t​a​t​e​_​P​S​t​a​t​e and then ACPI on Wiki
    we are experimenting with a new init system and it is fun
    linux init->upstart->systemd
    微信浏览器内建的WeixinJSBridge 实现“返回”操作
    npm i node-sass 报错&npm 镜像切换
  • 原文地址:https://www.cnblogs.com/gcdd1993/p/14667992.html
Copyright © 2020-2023  润新知