【剖析 | 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

目录
相关文章
|
Docker 容器
x86 平台利用 qemu-user-static 实现 arm64 平台 docker 镜像的运行和构建
x86 平台利用 qemu-user-static 实现 arm64 平台 docker 镜像的运行和构建
3162 1
|
开发工具 Docker 容器
Docker设置国内镜像源
Docker设置国内镜像源
19305 1
|
消息中间件 负载均衡 算法
聊聊 分布式 WebSocket 集群解决方案(二)
聊聊 分布式 WebSocket 集群解决方案
2274 0
聊聊 分布式 WebSocket 集群解决方案(二)
|
5月前
|
网络协议 NoSQL API
转转客服IM系统的WebSocket集群架构设计和部署方案
客服IM系统是转转自研的在线客服系统,是用户和转转客服沟通的重要工具,主要包括机器人客服、人工客服、会话分配、技能组管理等功能。在这套系统中,我们使用了很多开源框架和中间件,今天讲一下客服IM系统中WebSocket集群的的实践和应用。
519 141
|
4月前
|
人工智能 编解码 芯片
【AI绘画】你有多久没有打开SD了?
曾几何时,Stable Diffusion的复杂参数令人崩溃,如今即梦、可灵等AI工具已让生成图片变得轻而易举。哩布哩布发布2.0升级公告,看似迈向更易用的未来,却也悄然为那个钻研模型、拼接工作流的“拓荒时代”奏响终章。技术迭代飞快,但那份对创造的热爱与探索精神,永不褪色。
817 9
|
5月前
|
存储 算法 安全
“卧槽,系统又崩了!”——别慌,这也许是你看过最通俗易懂的分布式入门
本文深入解析分布式系统核心机制:数据分片与冗余副本实现扩展与高可用,租约、多数派及Gossip协议保障一致性与容错。探讨节点故障、网络延迟等挑战,揭示CFT/BFT容错原理,剖析规模与性能关系,为构建可靠分布式系统提供理论支撑。
298 2
|
11月前
|
运维 监控 NoSQL
【赵渝强老师】监控Redis
Redis 实例的监控是运维管理中的关键内容,主要包括内存、吞吐量、运行时信息和延时的监控。 1. **监控内存**:使用 `info memory` 可查看 Redis 内存使用情况,包括已用内存、峰值内存等。 2. **监控吞吐量**:通过 `info stats` 获取每秒处理命令数(OPS)、网络输入输出流量等。 3. **监控运行时信息**:利用 `info` 命令结合 `grep` 过滤出客户端连接数、拒绝连接数等重要信息。 4. **监控延时**:可以通过客户端手动监控或服务器内部延迟监控来检测延时问题。
381 3
|
存储 安全 Java
什么?CPU消耗要压降80% ——《事件序列化CPU开销压降》揭榜
本文为《事件CPU开销压降》揭榜报告,旨在解决风控系统间信息传递时事件体持续膨胀导致的序列化/反序列化CPU消耗过高的问题。
396 12
|
Linux 数据库
linux 全局搜索文件
在 Linux 系统中,全局搜索文件常用 `find`、`locate` 和 `grep` 命令。`find` 根据文件名、类型、大小、时间戳等条件搜索;`locate` 通过预构建的数据库快速查找文件;`grep` 在文件中搜索特定文本,常与 `find` 结合使用。选择合适的命令取决于具体需求。
2671 2