在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,不實現該接口即可)。

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM