序列化兼容技巧 之 指定序列化方式:
因为性能出色,Kryo 经常被选为Dubbo服务的序列化方案。
在序列化Java对象时,Kryo 默认使用 FieldSerializer 类进行序列化。
但为了增强兼容性,我们一般会显式指定使用 CompatibleFieldSerializer。
使用方式就是在 API jar 包中的类上,以添加注解的方式指定。
(只需对那些会在客户端和服务端之间进行传输的类进行改造)
如:
Java代码
-
package demo.api.entity;
-
-
import com.esotericsoftware.kryo.DefaultSerializer;
-
import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer;
-
-
@DefaultSerializer(CompatibleFieldSerializer.class)
-
public class Arg implements Serializable {
-
...
-
}
CompatibleFieldSerializer 是啥?
这是Kryo中一种序列化对象的方式。
它以直接赋值字段的方式进行序列化,达到 向前与向后 兼容。
也就是说,无论新版中增加还是删除字段,仍然能反序列化老版本所得数据。
因为直接赋值字段的方式可以跳过无法识别的字段。
当然,如果是字段类型发生变更,那就无法兼容了。
【代价】这种兼容性也是有代价的。
当一个类首次被序列化时,会分析并记下各字段名称。
在序列化和反序列化时,需要创建缓冲区进行分块编码。
这样才能跳过那些无法识别的字段。
应用场景举例
假设有一个Dubbo API jar包,其中有个参数类Arg,作为客户端向服务端传递信息的媒介。
1.0 版本中,Arg类有3个字段。2.0 版本中为了增强服务能力,需要在Arg类中新增1个字段 extField。
业务规定客户端可以不指定extField,由服务端使用默认值。即,业务允许新版本兼容老版本。
但是如果直接在Arg中增加该字段,服务端用 2.0 版本的jar包,客户端还是用 1.0 版本的jar包,
那么将导致Dubbo序列化失败。
怎么办?
方案一:从一开始就为Arg指定序列化方式 CompatibleFieldSerializer。
当然,如果 1.0 已经发布,且未将Arg的序列化方式指定为 CompatibleFieldSerializer,那么即使你在 2.0 中指定 Arg 的序列化方式,仍然会遇到序列化问题 —— 来自客户端的Arg中所有字段都未赋值。因为这两个版本的序列化方式不同。
1.0 使用的是默认的 FieldSerializer,2.0 用了 CompatibleFieldSerializer。
方案二:另起一个命名空间,存放新版Arg。相应的服务方法可以重载。
这样,原Arg类完全不变,序列化当然没问题。
事实上,很多组织喜欢用方案二。因为这种方式更利于清晰的管理,对代码侵入性也小。
思考
序列化兼容性一直是 Dubbo 服务治理的重要内容。
一般只有经历过各种项目迭代场景,才能有效规避Dubbo服务API研发中的坑。
这是Dubbo对使用者不够友好的重要特征之一。
其实 Martin Fowler 当初提到“微服务”时,表示微服务之间是以 HTTP API 之类的轻量级协议通信的。
Dubbo这样的“重量级”服务框架所呈现的“强约束”有利有弊。而序列化兼容性显然是绝大多数研发组织在治理Dubbo时所面临的一个重要课题。有无数人在此踩过各种坑。甚至因此抛弃Dubbo,使用其它替代方案。
当然也有组织依靠合格的项目管理水平、通透的业务研究能力等各种过硬的综合实力,规避了很多可能引发Dubbo序列化问题的低级错误的项目变更操作。
目前而言,大多数时候,Dubbo还是可以的。
当你因遇到并处理过很多Dubbo序列化问题,而自以为是“资深高级人才”时,很可能仅仅是因为自己所处的平台存在很薄弱的环节,导致自己不得不处理各种恶心的“人祸”。一个合格的研发人员应从各种角度综合考量整个项目。很多时候表面上是“技术问题”,但需要解决的却是“需求管理”、“业务架构”等其它方向的问题。