查看原文
其他

非标类型导致Dubbo接口出入参异常的本质 | 得物技术

羊羽 得物技术
2024-12-05

目录

一、概述

二、问题是什么?

    1. DTO中声明了类型模糊的字段

    2. DTO中赋值了非标集合类型数据

三、为什么会有这个问题?

    1. 对端(另一个进程)不存在的类型

    2. 无法通过反射实例化的类型

四、怎么处理这个问题?

    1. 调整序列化方案

    2. 包容它、迁就它

        2.1 标准库工具类

        2.2 lang3库工具类

        2.3 guava库工具类

五、总结

概述

笔者支持过程中多次发现诡异的Dubbo接口异常问题,抓耳挠腮最后定位到代码上和代码外的原因,事后只感觉脑瓜子嗡嗡的。考虑到这不是第一次,也绝不会是最后一次出现类似问题,下面笔者将尽可能详细的梳理、总结一下该问题的现象和本质。

问题是什么?

DubboRPC+Protostuff序列化场景下,如果DTO中的字段使用了非标集合类型,可能会导致接口出入参为nullerror,进一步导致业务受损。
该问题的产生存在如下前提或者门槛:
  1. 接口使用的是DubboRPC;
  2. 接口使用的是Protostuff序列化;
  3. DTO中声明了类型模糊的字段;
  4. 类型模糊的字段赋值了非标集合类型数据。

DTO中声明了类型模糊的字段

所谓类型模糊的字段,指的是在DTO中存在没法一眼判断出运行时具体类型的字段(静态分析)。听起来还是很抽象是吧?我们看下面的例子:
例1

例2

例3

信读者在看到上述案例的时候,肯定没法一口笃定字段obj3在运行时具体的、实际的类型是啥吧?如果机智如你肉眼都无法识别、判定这个字段的类型,那死板的序列化库(lib)代码更没法识别、判定。

你看到都迷糊,更别提代码了。

DTO中赋值了非标集合类型数据

要解释非标集合类或者非标准集合类,首先我们需要对齐标准集合类。
标准集合类指的是常见的、jdk标准库内自带的集合类。一般在java.util包下,实现了顶层的、公共的Interface,有公开的的构造方法。常见的有:

io.protostuff.CollectionSchema.MessageFactories

为什么会有这个问题?

如上述,当DTO中存在类型模糊的字段时,常见的序列化框架如Protostuff,没有办法根据类的定义和反射得到准确的数据类型。为了在序列化后(object -> byte[ ])能正常的、按预期完成反序列化(byte[ ] -> object),一般会在byte[ ]中添加类型提示,以辅助反序列化过程正常进行。
以如下DTO为例:
假设按如下代码构造上述Req:
我们用类json格式来进行序列化、反序列化推演,如果不做任何处理,序列化(object -> byte[ ])后的结果如下:

不管是json这种文本格式,还是Protostuff这种二进制格式,序列化产物(byte[ ])在逻辑结构上是相似的~

可以看到,序列化产物上没有任何类型信息(运行时信息),同时我们上面也看到,DemoReq类的obj3字段上也没有任何具体的类型提示(编译期信息)。那么对端收到上述数据进行反序列化(byte[ ] -> object)时,就无法恢复出正确的、类型和上游一致的字段对象。
Protostuff希望解决这个问题!Protostuff希望达成如下效果:
换句话说,Protostuff想保证:客户端的一个对象被序列化(object -> byte[ ])、传输到对端反序列化后(byte[ ] -> object),对端可以安全的对这个对象进行equals判断并以此进行业务逻辑处理。
为了实现这个目的,Protostuff在序列化产物上做了一些黑盒的、对上层业务透明的处理。在序列化产物的字段结构上添加类型提示,具体到java语言平台就是类的全限定名。
如上图,Protostuff在序列化产物(object -> byte[ ])中,将类型信息java.util.ArrayList关联到obj3字段上传输到对端,对端在反序列化(byte[ ] -> object)时,如果无法通过反射在DTO类字段(静态的、编译期信息)上提取数据类型时,会从序列化产物中提取字段类型,利用反射或硬编码创建实例后,再进行后续的数据解析、提取。
这就出现了一个问题:
如果在DTO中模糊字段上赋值了对端(另一个进程)不存在的类型或无法通过反射实例化的类型,那对象序列化、传输到对端后,对端进行反序列化(byte[] -> object)的过程就会异常!

对端(另一个进程)不存在的类型

如上图,Foo类是一个静态私有类,不在Api Jar中,传输到对端(另一个进程)后,对端反序列化时可能找不到该类(ClassNotFoundError)。

无法通过反射实例化的类型

如上图,Bar类是一个内部类,只能寄生于外部类,无法被单独实例化。对端反序列化时可能会找不到构造方法(NoSuchMethodException)或类状态不完整无法正常使用。

技术上来说,所有类都有手段进行实例化,比如使用sun.misc.Unsafe#allocateInstance,只是因为绕过了构造方法,实例化出来的类状态是不完整的,不能正常使用。

怎么处理这个问题?

调整序列化方案

调整到hessian2、json等代码质量更好、使用范围更广、社区更活跃的序列化方式~
需要注意,存量的很多业务接口依赖了Protostuff的模糊类型序列化特性,不能贸然切换到json、hessian2,需要进行完整的回归测试才能发布到生产环境。

包容它、迁就它

千言万语汇成一句话:避免在DTO中传输非标集合类型数据,或者传输之前进行标准集合类型转换!

你一定想问:为什么中间件同学不能向前一步cover这个问题,还得我们使用者注意,中间件同学的担当、责任感呢?

您的拷问很有道理!但是没有办法,或许Protostuff是为了追求极致的性能而完全抛弃了代码可读性、可维护性,中间件同学完全改不动、不敢改。

日常支持过程中,我们发现常用的、会产生非标集合类型数据的工具库、工具方式包括但不限于:

欢迎大家随时反馈相关的使用场景~

标准库工具类

java.util.ArrayList#subList
ArrayList<Integer> list = Lists.newArrayList(1, 2, 3);List<Integer> obj3 = list.subList(1, 2);
处置方法
ArrayList<Integer> list = Lists.newArrayList(1, 2, 3);List<Integer> obj3 = list.subList(1, 2);List<Integer> obj4 = new ArrayList<>(obj3);

lang3库工具类

org.apache.commons.collections.FastArrayList.SubList#subList
List<Integer> list = new FastArrayList(1, 2, 3);List<Integer> obj3 = list.subList(1, 2);
处置方法
ArrayList<Integer> list = Lists.newArrayList(1, 2, 3);List<Integer> obj3 = list.subList(1, 2);List<Integer> obj4 = new ArrayList<>(obj3);

guava库工具类

com.google.common.collect.Lists#transform
ArrayList<Integer> list = Lists.newArrayList(1, 2, 3);List<Integer> obj3 = Lists.transform(list, it -> it);
处置方法
ArrayList<Integer> list = Lists.newArrayList(1, 2, 3);List<Integer> obj3 = Lists.transform(list, it -> it);List<Integer> obj4 = new ArrayList<>(obj3);
com.google.common.collect.Lists#partition
ArrayList<Integer> list = Lists.newArrayList(1, 2, 3, 4);List<List<Integer>> obj3 = Lists.partition(list,2);
处置方法
ArrayList<Integer> list = Lists.newArrayList(1, 2, 3, 4);List<List<Integer>> lists = Lists.partition(list, 2) .stream() .map(Lists::newLinkedList) .collect(Collectors.toList());

总结

以上,笔者基于日常排障&答疑过程中的沉淀和深入到Protostuff源码的细节思考,总结了DubboRPC+Protostuff场景下常见的接口出参、入参为null或error的现象、本质和解决方案。笔者在这只能做到提纲挈领、抛砖引玉,希望可以帮大家理清关键环节和概念,更好的使用DubboRPC支持业务~

往期回顾


1. 得物千人规模敏捷迭代实践分享
2. AIGC 和低代码结合应用全栈研发实践总结 | 得物技术
3. 模型量化与量化在LLM中的应用 | 得物技术
4. 如何做配置链接的质量保障?看这篇就对了 | 得物技术
5. 大模型下B端前端代码辅助生成的思考与实践 | 得物技术


文 / 羊羽


关注得物技术,每周一、三、五更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。

扫码添加小助手微信

如有任何疑问,或想要了解更多技术资讯,请添加小助手微信:




线下活动推荐

快快点击下方图片报名吧!
继续滑动看下一个
得物技术
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存