类继承导致 RPC 调用 msgpack 序列化问题分析

Posted 松然聊技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类继承导致 RPC 调用 msgpack 序列化问题分析相关的知识,希望对你有一定的参考价值。

问题

996 工作态,晚上上线,业务调用方反馈通过 JSF(Jingdong Service Framework)RPC 调用的返回参数异常,获取服务列表的所有服务 service_status 为 0(0 表示已删除)。


分析

系统迅速回滚,返回参数正常。CodeReview 发现 API 的 Vo 父对象新增了属性,而 RPC 传输的子 Vo 继承了父 Vo,虽然,新增的属性是放在父 Vo 的末尾,但为 JSF 的序列化方式为 msgpack,由于业务调用方未更新父 Vo,所以导致了子 Vo 反序列化的异常。

为什么 Vo 的反序列化会失败?而不是像 JSON、Hession 可以向下兼容?

首先,我们要先了解一下 msgpack 的原理。


msgpack

msgpack 用官方的话说,msgpack 是一种高效的二进制序列化方式。

基于官方的解释:JSON 为什么会变小?

核心压缩方式可参看官方说明 messagepack specification:

https://github.com/msgpack/msgpack/blob/master/spec.md

简单的总结下,msgpack 是按字段顺序进行序列化和反序列化的,优点是速度快,缺点是无法改变字段顺序。


msgpack 字段兼容规则

在两边不同时升级的情况下,字段兼容规则如下:(包括 Bean 和枚举)

  • 1、不能调整原有字段顺序,不能删减字段,除非是删最后一个字段

  • 2、新加的字段必须在字段最后面(只是字段顺序,不是文件最后面,getter/setter 方法等随意)

  • 3、父类的字段不能变,因为父类一变相当于子类的中间插入一个字段

满足上面规则,服务端和客户端哪边先升级都无所谓。

如果是需要父类加字段,或者中间加减字段这种,则需要服务端和调用端同时升级。

基于此的理解,我们在看下类继承导致 RPC 调用 msgpack 序列化问题分析:

msgpack VS hessian

Hessian 序列化的时候,会写入字段名称,然后字段值,可以想象为一个 map。

MsgPack 序列化的时候,不写入字段名字,会按字段顺序写入值,可以想象为一个数组。


解决

方案一:Vo 不采用继承,对 Vo 设置 final 属性

方案二:不使用 msgpack 序列化方式,改成 hessian 序列化方式


总结

其实,之前使用 msgpack 序列化方式也出现过类似的问题,如在 Vo 中间新增属性,导致序列化失败,这次的问题之所以没有异常,是因为父 Vo 中新增的属性也是 int 类型,这导致序列化过程中虽然没有赋值,但采用里默认值 0。所以,并没有出现异常,而是结果值错误。

RPC 框架在分布式系统中担任着越来越重要的角色,如阿里的 duboo、京东的 jsf、谷歌的 gRPC,序列化的方式也多种多样,如 json、hessian、msgpack、protobuf 等等,这也需要我们在使用过程中了解其序列化的原理,不断精进。


鸣谢

感谢张松然对本文章的校订。

以上是关于类继承导致 RPC 调用 msgpack 序列化问题分析的主要内容,如果未能解决你的问题,请参考以下文章

MsgPack/Json性能数据

rpc框架yar之源码解析- 打包(json, msgpack, serialize)

再谈序列化之rpc调用失败和jackson序列化时不允许Map中的key为null

Dubbo 2.7.12发布,分布式RPC服务框架

什么反序列化 GWT RPC 调用客户端

架构面试:RPC原理的考查点