【剖析 | SOFARPC 框架】系列之SOFARPC跨语言支持剖析-阿里云开发者社区

开发者社区> 金融级分布式架构> 正文

【剖析 | SOFARPC 框架】系列之SOFARPC跨语言支持剖析

简介: Scalable Open Financial Architecture 是蚂蚁金服自主研发的金融级分布式中间件,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。

SOFA
Scalable Open Financial Architecture
是蚂蚁金服自主研发的金融级分布式中间件,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。

本文为《剖析 | SOFARPC 框架》第十二篇,作者鸥波。

《剖析 | SOFARPC 框架》系列由 SOFA 团队和源码爱好者们出品,
项目代号:,官方目录目前已经全部认领完毕,文末提供了已完成的文章目录。


image.png

前言

随着 TIOBE 10月份的编程语言排行的发布,C++ 重回第三的位置,新兴的 Swift 和 Go 表现出强劲的上升趋势。虽然目前 Java 的领头位置尚未出现有力挑战,我们希望能够在基础设施的建设上预留跨语言的可扩展设计。同时,跨语言的挑战也是工程实际面临的现状,蚂蚁内部如 AI、IoT,算法等缺少 JVM 原生支持的领域,往往不可避免地需要涉及到跨语言调用的问题。

本文将为大家介绍 基于 SOFARPC 的微服务应用在面临跨语言调用时的方案和实现。

总体设计

经过前面几篇对 SOFARPC 的 BOLT 协议和序列化这些的介绍,相信大家已经对 RPC 有了一些理解,提到跨语言,我们会首先想到其他语言调用 Java,Java 调用其他语言,那么这里的跨,体现在代码上,到底跨在哪里?

从跨语言的实现上来说,主要解决两个方面的问题:

  • 跨语言的通讯协议和序列化协议
  • 跨语言服务发现

另外从跨语言的落地来说,还得解决一个平滑兼容的问题。

业界常见的做法是一般是通过 DNS 和 HTTP 来解决跨语言的问题,但是在内部已经有完善技术栈体系的情况下,直接切换一个新的方案显然是不合适的,所以蚂蚁内部是在已有的技术体系基础上进行改进。

蚂蚁内部使用的通讯协议是 Bolt,序列化协议是 Hessian。我们知道,服务端和客户端在请求和返回之间携带的结构化的业务数据,需要在传输到达对端后,被本地的语言能够易于解析消费。由于语言本身特性的差异,同一对象的在序列化和反序列化的转换后,结构可能有差异,但是需要保证其转换操作是可逆的。以上这点Hessian做的不是很好,其跨语言的兼容性不能满足跨语言的需求,所以另外一个可行的方案就是就是选择其它基于 IDL 的序列化协议,例如 Protobuf。

现成的服务注册中心一般都有一些多语言解决方案,像 Zookeeper、SOFARegistry、Consul、etcd 等都有多语言客户端,所以服务发现这块问题不算太大。

例如下面就是一个基于注册中心 + Bolt协议 + Protobuf 序列化的设计图

image.png

通讯协议和序列化协议

通讯协议只要跨语言各方约定清楚,大家安装约定实现即可,而序列化协议则需要较多的考量。

序列化的协议选择列出一些考虑要点:

  • 是否采用具备自我描述能力的序列化方案,如不需要借助一些 schema 或者接口描述文件。
  • 是否为语言无关的,包括脚本语言在内。
  • 是否压缩比例足够小,满足网络传输场景的要求。
  • 是否序列化和反序列化的性能均足够优秀。
  • 是否向前/向后兼容,能够处理传输对象的新增属性在服务端和客户端版本不一致的情况。
  • 是否支持加密、签名、压缩以及扩展的上下文。

1、JSON Over HTTP
首先,说到跨语言,序列化支持,肯定有同学会问,为什么不直接通过 Http的Json来搞定呢?

虽然得益于JSON和HTTP在各个语言的广泛支持,在多语言场景下改造支持非常便捷,能够低成本的解决网络通讯和序列化的问题。服务发现的过程则可以使用最简单的固定URL(协议+域名+端口+路径)的形式,负载均衡依赖于F5或者LVS等实现。

但是这个方案的有明显的局限性:

  • HTTP 作为无状态的应用层协议,在性能上相比基于传输层协议(TCP)的方案处于劣势。HTTP/1.1后可以通过设置keep-alive使用长连接,可以一定程度上规避建立连接的时间损耗;然而最大的问题是,客户端线程采用了 request-response 的模式,在发送了 request 之后被阻塞,直到拿到 response 之后才能继续发送。这一问题直到 HTTP/2.0 才被解决。
  • JSON 是基于明文的序列化,较二进制的序列化方案,其序列化的结果可读性强,但是压缩率和性能仍有差距,这种对于互联网高并发业务场景下,意味着硬件成本的提升。

对于网络变化的响应。订阅端处理不够强大。

2、Hessian Over BOLT
在否决了上一个方案后,我们继续看,蚂蚁内部,最开始的时候,SOFARPC 还没有支持 Protobuf 作为序列化方式,当时为了跨语言,NodeJs的同学已经在此基础上,用 js 重写了一个 hessian 的版本,完成了序列化。也已经在线上平稳运行。但是当我们要扩展给其他语言的时候,重写 hessian 的成本太高。而且 Java语言提供的接口和参数信息,其他语言也需要自己理解一遍,对应地转换成自己的语言对象。因此该方案在特定场景下是可行的。但不具备推广至其他语言的优势。

Node的实现版本可以参考:https://github.com/alipay/sofa-rpc-node

3、Protobuf Over BOLT
Protobuf 基于 IDL,本身具备平台无关、跨语言的特性,是一个理想的序列化方案。但是需要先编写proto文件,结构化地描述传输的业务对象,并生成中间代码。

由于要重点介绍一下这种方案,因此再次回顾一下 SOFABolt 的协议规范部分,便于后面的解释。

image.png

对于现有的通信协议,我们改进时,将 content 部分存储为入参对象和返回值,他们都是 pb 序列化之后的值。这样将直接对接到现在的协议上。又利用了 BOLT 的通信协议。

以下描述了跨语言中对 Protobuf协议的使用:

image.png

首先我们看 header 部分,是简单的扁平化的 KV。默认会增加以下三个 Entry:

image.png

我们再看 body 部分,根据 Protobuf 的实现,所有被序列化的对象均实现了 MessageLite 接口,然而由于多个 Classloader 存在的可能,代码上为了避免强转 MessageList 接口的失败,并未直接调用 toByteArray 方法,而是通过反射机制调用 toByteArray 获得 byte 数组。

针对 SofaRequest 这个 RPC 中的传输对象,由于 Protobuf 仅支持对于单个对象的序列化,因此 SofaRequest 类型的对象进行序列化,实际支持的是 SofaRequest#methodArgs 数组中的首个元素对象进行的序列化,也就是说目前我们仅支持一个入参对象。

针对 SofaResponse 这个响应对象,当出现框架异常或者返回对象是一个 Throwable 代表的业务异常时,直接将错误消息字符串序列化;并在响应头中设置 sofa_head_response_error=true,其他情况才序列化业务返回对象。这样可以避免比如 Java 语言的错误栈,由于含有 一些线程类和异常类,其他语言是无法解析的。

反序列化的过程稍复杂一些,上游调用传入 SofaRequest/SofaResponse 的实例,先要在空白的 SofaRequest 对象中填入前文中在 header 反序列化中的解析的头部信息,接着根据 Header 中接口+方法名找到等待反序列化对象的 class,并借助反射调用 parseFrom 接口生成对象,成为 SofaRequest#MethodArgs 的首个元素对象。

4、Others Over BOLT
在上一个方案的基础上,我们也可以支持更多的语言,对 JSON、Kyro 的支持也分别处于开发和规划中。 JSON 的支持已经开发完成待合并。这里不再做过多说明。

服务发现

跨语言各方约定了通讯协议和序列化协议后,就可以完成各自的服务端和客户端实现,跨语言已经能完成点对点的调用了。但在实际的线上场景下,我们还是需要通过注册中心等服务发现的形式,来保证跨语言调用的可用性。目前,有两种可选的方案。

1、各语言对接注册中心

对于服务发现,前面说到的最早进行跨语言的 NodeJs 实现了对接 SOFARegistry 的能力。直接通过对 Java 原生序列化和一些 hessian 的重写,来操作完成了。在蚂蚁内部,这种方案在只有 Node 的情况下是可以的,但是更通用的场景下。如果我们有了新的注册中心,要对接更多的注册中心,其他语言在语言表达上的差异性,使得这种方案很难推广到其他项目。NodeJs 版本的 hessian:https://github.com/alipay/sofa-hessian-node

2、各语言对接SOFAMosn

由于每个语言都去对接对接中心存在一定的难度,也不具备可推广性,而在蚂蚁内部,我们已经在一些跨语言的场景下,运行 SOFAMosn,通过 SOFAMosn,我们对接了站内的注册中心,其他的语言,仅需要将自己需要订阅和发布的信息,通过 Http 的接口形式,通知 SOFAMosn,SOFAMosn 将会将这些信息和注册中心进行注册和订阅,并维持地址信息。

这样对于其他语言来说,仅需要非常简单的 json请求,就完成了跨语言的服务注册和订阅。后续新注册中心的对接等等。其他语言都不再需要理解。相关的 SDK 我们已经开发并实现完成。对于 SOFAMosn 的更多介绍,可以参看 SOFAMosn 官网:

image.png

当然如果你并不需要进行服务寻址,或者能够接受硬负载或者固定 IP的调用方式。也可以直接使用。

参考资料
SOFA 微服务多语言演进

SOFARPC 框架之总体设计与扩展机制

蚂蚁通信框架实践

JVM Serializers :

https://github.com/eishay/jvm-serializers/wiki

结语

至此,我们介绍了 SOFARPC 中对于 Protobuf 的跨语言实现,并介绍了一些 NodeJs 对跨语言的支持,最后介绍了我们用 SOFAMosn 实现通用的服务发现。

在大多数场景下,我们更推荐是使用 SOFAMosn 来做服务寻址,这样之后 Mosn 层面的一些限流熔断,也可以在多语言上进行使用。

而对一些场景比较简单,能够容忍固定 IP 调用,或者使用硬件负载均衡设备的。也可以直接使用各个跨语言客户端,进行直接开发调用。

相关链接

SOFA 文档: http://www.sofastack.tech/

SOFA: https://github.com/alipay

SOFARPC: https://github.com/alipay/sofa-rpc

SOFABolt: https://github.com/alipay/sofa-bolt

SOFAMosn: https://github.com/alipay/sofa-mosn

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

SOFAStack™(Scalable Open Financial Architecture Stack)是一套用于快速构建金融级分布式架构的中间件,也是在金融场景里锤炼出来的最佳实践。

官方博客
官网链接