本文是使用Dubbo过程中的一些思考,不足以作为参考,只为留存记录。

前言

Dubbo官方推荐使用kryo或者fst作为RPC序列化协议,原因是这两款序列化协议,性能显著优于其他序列化协议。虽然高效,但其实Dubbo对这两款的支持却不是那么理想,导致使用过程中出现了一些问题

多服务下kryo序列化问题

Q1、无法使用kryo类索引红利

在多模块协作的系统中,无法保证服务提供方和消费方DTO数量以及注册顺序的一致,可能A作为服务提供方,提供了DTO,而B作为消费方,可能同时消费A、C的服务,同时自身也对外提供服务,在使用Kryo序列化时,需要同时注册A、B、C服务的DTO

例如A系统的SerializationOptimizer实现

1
2
3
4
5
6
7
8
9
10
11
public class ASerializationOptimizerImpl implements SerializationOptimizer {
@Override
public Collection<Class<?>> getSerializableClasses() {
return A1.class;
return A2.class;
return A3.class;
return A4.class;
return C1.class;
return C2.class;
}
}

B系统的SerializationOptimizer实现

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BSerializationOptimizerImpl implements SerializationOptimizer {
@Override
public Collection<Class<?>> getSerializableClasses() {
return A1.class;
return A2.class;
return A3.class;
return A4.class;
return B1.class;
return B2.class;
return C1.class;
return C2.class;
}
}

这里需要说明的是,kryo之所以高效,不仅仅是因为其二进制序列化,更由于其可以预先为待序列化的类指定索引,在RPC传输过程中,只需要使用索引代替全类名,在类使用非常频繁的情况下,可以节省大量字节,从而大大提升传输效率。

Dubbo似乎也意识到了这个问题,所以将一些使用频繁的类进行了预处理,见org.apache.dubbo.common.serialize.kryo.utils.AbstractKryoFactory#create

但是其中很重要的一处

1
2
3
4
5
6
7
SerializableClassRegistry.getRegisteredClasses().forEach((clazz, ser) -> {
if (ser == null) {
kryo.register(clazz);
} else {
kryo.register(clazz, (Serializer) ser);
}
});

使用的是kryo.register(clazz),无法指定kryo class index,这里我分析了一下,可能是为了兼容老的序列化协议,比如json等,而且SerializableClassRegistry是很早就有的接口,并没有考虑到类索引的问题。

所以这里要注册的话,只能将项目内所有类都写到同一个jar中,然后写一个统一的SerializationOptimizer实现。

这在微服务系统中是无法实现的,因为每个服务必然会提供自己的dto.jardubbo-service.jar,而且服务之间也不可能依赖其他所有的服务,所以这一条无法满足。

这样一来,类的注册顺序不能保证,类的数量也无法保证,所以Dubbokryo序列化只能说在一定程度上提升了效率,但是并没有完全发挥出kryo的性能优势。

Q2、社区不活跃

虽然阿里重启了Dubbo,并且加入了Apache进行孵化,但是社区相较于Spring Cloud,还是不够活跃,而且阿里的开源产品,多多少少带了点阿里内部的味道,不如Spring Cloud通用和考虑周全。一些功能我们不需要(比如dubbo注册时的一堆参数,很多都是需要阿里的一套开发体系才能使用到),我们需要的又迟迟不添加(kryoClassId支持)。

所以目前项目已经全面切换到Spring Cloud Kubernetes,有时间也会总结一下Dubbo切换到Spring Cloud的经验,以及在云原生时代Spring Cloud所做出的努力。

相关资料