






















SOFA Scalable Open Financial Architecture 是蚂蚁金服自主研发的金融级分布式中间件,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。
本文为《剖析 | SOFARPC 框架》第七篇,作者莫那·鲁道 ,来自 E签宝。 《剖析 | SOFARPC 框架》系列由 SOFA 团队和源码爱好者们出品, 项目代号:<SOFA:RPCLab/>,官方目录目前已经全部认领完毕。
我们知道,在 RPC 调用中,客户端需要加载服务端提供的接口定义类,但是,很多情况下,这个并不总是可行的,于是,衍生了泛化调用的需求,一个成熟的,功能完善的 RPC 框架一般都会支持泛化调用,那么什么是泛化调用呢?SOFA RPC 又是如何支持泛化调用的?同时又是如何实现的? 和其他的 RPC 泛化调用又有何不同?有何优势?我们将在本文一一解答这些问题。
当客户端因为某种原因无法得到服务提供方的接口 jar 包时,或者是客户端是一个比较通用的系统,并不想依赖每个服务提供方提供的 facade接口,但是又需要进行调用,那么此时就需要进行泛化调用。
例如:
那么这种情况下,肯定不能包含所有接口的 jar 文件,否则就太臃肿了。实际上也是不现实的,总不能每增加一个服务端,就增加一个 jar 包依赖,然后应用进行发布重启。
这个时候就可以使用泛化调用,将相应的请求包装成泛化调用,就能够实现不依赖接口 jar 包,多语言调用 RPC 服务,避免重复开发。
SOFA RPC 的官方文档十分详细,在官方 wiki 泛化调用 中,已有详细介绍。同时,在源码中的 example 模块中,也有现成的 demo 可以跑起来,读者可以自己 clone 源码阅读,这里我们简要说明一下使用方式,以便大家有一个直观的了解。
总的来说,泛化调用有 2 个 API,包含 5 个方法,其中, 2 个方法已经废弃,也就是说,有 3 个主要方法。分别是:
/**
* 泛化调用
* @return 正常类型(不能是GenericObject类型)
*/
Object $invoke(String methodName, String[] argTypes, Object[] args);
/**
* 支持参数类型无法在类加载器加载情况的泛化调用
* @return 除了JDK等内置类型,其它对象是GenericObject类型
*/
Object $genericInvoke(String methodName, String[] argTypes, Object[] args);
/**
* 支持参数类型无法在类加载器加载情况的泛化调用
* @return 返回指定的T类型返回对象
*/
<T> T $genericInvoke(String methodName, String[] argTypes, Object[] args, Class<T> clazz);
基本上,已经覆盖了常用的集中场景,可以说功能相当全面。
由于篇幅有限,这里就不贴使用 demo 了,感兴趣的可以通过链接查看官方的 demo 或者源码,包含 SOFARPC 的 API 使用方式和 SOFABoot 的使用方式:
接下来我们重点来介绍 SOFARPC 是如何实现泛化调用的。
简单来说,泛化调用的关键就是对象表示和序列化,SOFARPC 提供了 GenericObject 等对象来表示参数对象或者返回值对象,而将 GenericObject 对象序列化成目标对象,或者将返回值反序列化成 GenericObject 对象,是 SOFARPC 实现泛化的关键。
这里我们先来看一下 SOFARPC 泛化调用的流程图,有助于后面理解泛化实现。

我们来说一下这个图:
从上面的流程可以看出,序列化器在泛化调用中,占了极大的篇幅和作用。而 SOFARPC 针对泛化调用,对 hessian3 进行了改造,使其支持泛化调用所需要的序列化功能。
SOFA-Hessian 的改动可以参考这里。
SOFA-Hessian 在 hessian 的包中加入了 com.alipay.hessian.generic 包,此包的作用就是处理泛化调用,重写的关键是实现或继承 SerializerFactory 类和 Serializer、Deserializer 等接口。在这里,设计了一下几个类,来描述 中对应的类型信息,同时实现这几个类的序列化和反序列化。对应关系如下

我们以 GenericObjectSerializer 为例,该序列化器重写了 writeObject 方法,该方法的作用就是将 GenericObject 对象序列化成目标对象字节流。即,拿出 GenericObject 的 type 字段和 fields 字段,组装成目标对象的字节流。
例如: 有一个类型是的 RPC 对象
public class TestObj {
private String str;
private int num;
}
在泛化调用客户端,可以直接构造一个 GenericObject对象
GenericObject genericObject = new GenericObject(
"com.alipay.sofa.rpc.invoke.generic.TestObj");
genericObject.putField("str", "xxxx");
genericObject.putField("num", 222);
此时,GenericObjectSerializer 就可以通过这些信息,将 GenericObject 对象转成 TestObj 对象的字节流。
服务提供方就可以通过普通的 hessian2 反序列化得到对象。
相比较其他 RPC 框架两端都需要对泛化进行支持,SOFARPC 显得要友好的多。也就是说,如果应用想要支持泛化,只需要升级客户端(消费者)即可,服务端(提供者)是无感知的。因为在服务端看来,收到的对象是完全一致的。你可能觉得对于复杂类型,写出这样一个构造是很困难的。SOFA-Hessian中已经提供了一个工具类
com.alipay.hessian.generic.util.GenericUtils
来辅助使用者来生成,可以直接使用。
下面我们来介绍下泛化调用和业界一些其他产品的比较,首先介绍一下序列化本身的一些性能和优势比较。
在 github 上,有一个专门针对 Java 序列化进行的 benchmark,可以稍微做一下参考。虽然在实际的场景中, 每个序列化的场景不同,带来的结果可能和这里的 benchmark 结果不同,但还是有参考意义,从该项目的基准测试可以看出:Json 无论是压缩比还是序列化时间,相比 hessian 等都有相当大的劣势。
同时,虽然 hessian 相对于 protostuff、kryo 等在性能上有一点差距,但是 hessian 反序列化无需指定类型,这个优势是非常有价值的。
在众多的 RPC 框架中,Dubbo 也提供了泛化调用的功能,接下来我们再来说说 Dubbo 的泛化。Dubbo 泛化和 SOFA RPC 泛化最大的不同就是:Dubbo 需要服务端也支持泛化,因此,如果想提供泛化功能,服务端也必须进行升级,这看起来可能没有 SOFA RPC 友好。
Dubbo 的泛化调用流程如下图:

可以看到,Dubbo 的服务端也需要泛化过滤器将 Map 解析成 POJO 来解析数据。
本文主要讲解了 SOFARPC 泛化调用的设计与实现,介绍了泛化调用的场景,同时,提及了 SOFA RPC 泛化调用的 API 使用,也详细讲解了 SOFARPC 的泛化设计和实现。最后,对社区中的一些 RPC 框架的泛化调用做了简单的比较。
这里对SOFARPC 的泛化设计与实现做个小结:
https://github.com/eishay/jvm-serializers
https://github.com/alipay/sofa-hessian
http://www.sofastack.tech/sofa-rpc/docs/Generic-Invoke
文章转自【剖析 | SOFARPC 框架】之SOFARPC 泛化调用实现剖析
注意: 由于原文章链接发生变更,因此将链接更新为 sofastack 官网,可在官网查询相关文章.
扫描下面的二维码关注我们的微信公众号,第一时间查看最新内容。同时也可以关注我的Github,看看我都在了解什么技术,在页面底部可以找到我的Github。

此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。