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

问题

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

分析

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

类继承导致 RPC 调用 msgpack 序列化问题分析_第1张图片

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

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

msgpack

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

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

类继承导致 RPC 调用 msgpack 序列化问题分析_第2张图片

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

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

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

msgpack 字段兼容规则

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

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

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

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

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

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

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

类继承导致 RPC 调用 msgpack 序列化问题分析_第3张图片

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 序列化问题分析)