Kryo是什么?
Kryo是用于Java的快速高效的二进制对象图序列化框架。
该项目的目标是高速,小尺寸和易于使用的API。不管是将对象持久保存到文件、数据库还是通过网络传输时,都可以尝试Kryo。
Kryo还可以执行自动的深浅复制/克隆。这是从对象到对象的直接复制,而不是从对象到字节的复制。
具体可以参考Kryo官网
在Dubbo中使用Kryo
本文基于Dubbo版本2.7.8
Dubbo支持非常多的序列化方式,如hession2
、avro
、FST
等等,其中Dubbo官网推荐的序列化方式是Kryo
,因为Kryo
是一种非常成熟的序列化实现,已经在Twitter、Groupon、Yahoo以及多个著名开源项目(如Hive、Storm)中广泛的使用。
开始
在Dubbo中使用Kryo非常方便,首先引入依赖
1 2 3 4
| implementation 'de.javakaffee:kryo-serializers:0.43'
implementation 'com.esotericsoftware:kryo:4.0.2'
|
如果只是简单使用,引入kryo即可,如果要支持一些例如List接口,则需要引入kryo-serializers,它针对一些特殊类为Kryo做了适配。
配置
在Dubbo中启用Kryo序列化方式,这里使用SpringBoot YML配置方式
1 2 3
| protocol: serialization: kryo optimizer: org.hmwebframework.microservice.dubbo.serialize.SerializationOptimizerImpl
|
其中org.hmwebframework.microservice.dubbo.serialize.SerializationOptimizerImpl
是指定Kryo序列化类,例如
1 2 3 4 5 6 7 8 9 10 11 12
| 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
1 2 3 4 5 6 7 8 9
| public interface SerializationOptimizer {
Collection<Class<?>> getSerializableClasses(); }
|
提供了一个接口方法,用于获取序列化的java类型列表,在DubboProtocol#optimizeSerialization
中被使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private void optimizeSerialization(URL url) throws RpcException { try { Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className); 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; } 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
1 2 3 4 5 6 7 8 9 10 11 12
| for (Class clazz : registrations) { kryo.register(clazz); }
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,例如
1 2 3 4
| 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,并提供一个注册类列表。随着项目规模扩大,不可能时时刻刻想着维护这个注册类列表,所以我们可以使用反射来自动获取这个注册类列表
引入依赖
1 2
| implementation 'org.reflections:reflections:0.9.11'
|
编写接口,
1 2 3
| public interface KryoDubboSerializable extends Serializable { }
|
编写SerializationOptimizer实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @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() .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; }
protected abstract String[] basePackage();
}
|
每次使用时,只需要继承AbstractSerializationOptimizerImpl,并提供待注册包路径(支持多个),待注册的类需要实现KryoDubboSerializable接口,这是为了在一定程度上提升灵活性(如果不需要注册到Kryo,不实现该接口即可)。
参考