打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
【剖析 | SOFARPC 框架】
Scalable Open Financial Architecture
是蚂蚁金服自主研发的金融级分布式中间件,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。
本文为《剖析 | SOFARPC 框架》第十二篇,作者鸥波。
《剖析 | SOFARPC 框架》系列由 SOFA 团队和源码爱好者们出品,
项目代号:,官方目录目前已经全部认领完毕,文末提供了已完成的文章目录。
前言
随着 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 序列化的设计图
通讯协议和序列化协议
通讯协议只要跨语言各方约定清楚,大家安装约定实现即可,而序列化协议则需要较多的考量。
序列化的协议选择列出一些考虑要点:
是否采用具备自我描述能力的序列化方案,如不需要借助一些 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 的协议规范部分,便于后面的解释。
对于现有的通信协议,我们改进时,将 content 部分存储为入参对象和返回值,他们都是 pb 序列化之后的值。这样将直接对接到现在的协议上。又利用了 BOLT 的通信协议。
以下描述了跨语言中对 Protobuf协议的使用:
首先我们看 header 部分,是简单的扁平化的 KV。默认会增加以下三个 Entry:
Key
Value
备注
sofa_head_method_name
对方方法名
对应 SofaRequest#methodName
sofa_head_target_app
对方的应用名
对应 SofaRequest#targetAppName
sofa_head_target_service
对方的服务唯一命名
对应 SofaRequest#targetServiceUniqueName
sofa_head_response_error
true/false
仅在响应中出现
我们再看 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 官网:
http://www.sofastack.tech/sofa-mosn/docs/README
语言
实现
python
https://github.com/alipay/sofa-bolt-python
node
https://github.com/alipay/sofa-rpc-node
c++
https://github.com/alipay/sofa-bolt-cpp
当然如果你并不需要进行服务寻址,或者能够接受硬负载或者固定 IP的调用方式。也可以直接使用。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
APP为什么用JSON协议与服务端交互:序列化相关知识
中国人自己的RPC
蚂蚁金服RPC框架SOFA
几种序列化协议(protobuf,xstream,jackjson,jdk,hessian)相关数据对比
【RPC 专栏】深入理解RPC之序列化篇--总结篇
ProtoBuf 常用序列化/反序列化API
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服