RPC(1)- 简介

最后更新:2020-05-15

1. RPC

RPC(Remote Procedure Call),即远程过程调用,是一个分布式系统间通信的必备技术。分布式系统中的网络通信一般都会采用四层的TCP协议或七层的HTTP协议

HTTP协议,以其中的Restful规范为代表,它可读性好,且可以得到防火墙的支持、跨语言的支持。缺点是有用信息占比少,效率低,毕竟HTTP工作在第七层,包含了大量的HTTP头等信息。

RPC 最核心要解决的问题就是在分布式系统之间间,如何执行另外一个地址空间上的函数,就仿佛在本地调用一样。

  • 屏蔽远程调用跟本地调用的区别,让我们感觉就是调用项目内的方法
  • 隐藏底层网络通信的复杂性,让我们更专注于业务逻辑。

一个典型 RPC 的使用场景中,包含了服务发现、负载、容错、网络传输、序列化等组件,其中“RPC 协议”就指明了程序如何进行网络传输和序列化。

2. RPC核心功能

RPC 的核心功能是指实现一个 RPC 最重要的功能模块,就是上图中的”RPC 协议”部分

要让网络通信细节对使用者透明,我们需要对通信细节进行封装,我们先看下一个RPC调用的流程涉及到哪些通信细节:

  1. 服务消费者(Client 客户端)通过本地调用的方式调用服务。
  2. 客户端存根(Client Stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体。
  3. 客户端存根(Client Stub)找到远程的服务地址,并且将消息通过网络发送给服务端。
  4. 服务端存根(Server Stub)收到消息后进行解码(反序列化操作)。
  5. 服务端存根(Server Stub)根据解码结果调用本地的服务进行相关处理
  6. 服务端(Server)本地服务业务处理。
  7. 处理结果返回给服务端存根(Server Stub)。
  8. 服务端存根(Server Stub)序列化结果。
  9. 服务端存根(Server Stub)将结果通过网络发送至消费方。
  10. 客户端存根(Client Stub)接收到消息,并进行解码(反序列化)。
  11. 服务消费方得到最终结果。

RPC的目标就是要2~10这些步骤都封装起来,让用户对这些细节透明。

上图也说明了RPC核心的重要组成:

  1. 客户端(Client):服务调用方。
  2. 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
  3. 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
  4. 服务端(Server):服务的真正提供者。
  5. Network Service:底层传输,可以是 TCP 或 HTTP

3. RPC 核心功能的实现

RPC 的核心功能主要由 5 个模块组成

  1. 透明化远程服务调用:字节码生成,JDK动态代理
  2. 编码和解码
  3. 服务寻址:Call ID 映射,可以直接使用函数字符串,也可以使用整数 ID。映射表一般就是一个哈希表。
  4. 数据流的序列化和反序列化:可以自己写,也可以使用 Protobuf 或者 FlatBuffers 之类的。
  5. 网络传输:在 RPC 中可选的网络传输方式有多种,可以选择 TCP 协议、UDP 协议、HTTP 协议

3.1. 透明化远程服务调用

Java通过代理封装通信细节让让用户像以本地调用方式调用远程服务。Java代理有两种方式:

  • jdk 动态代理
  • 字节码生成

尽管字节码生成方式实现的代理更为强大和高效,但代码维护不易,大部分公司实现RPC框架时还是选择动态代理方式。

动态代理的介绍查看这篇文章

3.2. 编码和解码

通过动态代理封装了通信细节,而通信的第一步就是要确定客户端和服务端相互通信的消息结构。

客户端的请求消息结构一般需要包括以下内容:

  • 接口名称,服务端会有很多接口
  • 方法名,一个接口内可能有很多方法
  • 参数类型&参数值,参数类型有很多,比如有bool、int、long、double、string、map、list,甚至如struct(class);以及相应的参数值;
  • 超时时间
  • requestID,标识唯一请求id

服务端返回的消息结构一般包括以下内容。

  • 返回值
  • 状态code
  • requestID

3.3. 服务寻址

服务寻址可以使用 Call ID 映射。

在本地调用中,函数体是直接通过函数指针来指定的,但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以在 RPC 中,所有的函数都必须有自己的一个 ID。这个 ID 在所有进程中都是唯一确定的。

客户端在做远程过程调用时,必须附上这个 ID。然后我们还需要在客户端和服务端分别维护一个函数和Call ID的对应表。

当客户端需要进行远程调用时,它就查一下这个表,找出相应的 Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。

3.4. 数据流的序列化和反序列化

序列化可以简单理解为对象 –> 字节的过程,同理,反序列化则是相反的过程。这一过程的目的可以理解为转义,然后方便传输。

在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用 C++,客户端用 Java 或者 Python)。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。

现如今序列化的方案越来越多,每种序列化方案都有优点和缺点,它们在设计之初有自己独特的应用场景。从RPC的角度上看,主要看三点:

  • 通用性,比如是否能支持Map等复杂的数据结构;
  • 性能,包括时间复杂度和空间复杂度,由于RPC框架将会被公司几乎所有服务使用,如果序列化上能节约一点时间,对整个公司的收益都将非常可观,同理如果序列化上能节约一点内存,网络带宽也能省下不少;
  • 可扩展性,对互联网公司而言,业务变化飞快,如果序列化协议具有良好的可扩展性,支持自动增加新的业务字段,而不影响老的服务,这将大大提供系统的灵活度。

目前互联网公司广泛使用Protobuf、Thrift、Avro等成熟的序列化解决方案来搭建RPC框架,这些都是久经考验的解决方案。

3.5. 网络传输

远程调用往往用在网络上,客户端和服务端是通过网络连接的。消息数据结构被序列化为二进制串后,下一步就要进行网络通信了。

所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把 Call ID 和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。 只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。

在 RPC 中可选的网络传输方式有多种,可以选择 TCP 协议、UDP 协议、HTTP 协议,每一种协议对整体的性能和效率都有不同的影响。

如何选择一个正确的网络传输协议呢?首先要搞明白各种传输协议在 RPC 中的工作方式。

  • 基于TCP协议的RPC调用

由服务的调用方与服务的提供方建立 Socket 连接,并由服务的调用方通过 Socket 将需要调用的接口名称、方法名称和参数序列化后传递给服务的提供方,服务的提供方反序列化后再利用反射调用相关的方法,最后将结果返回给服务的调用方

  • 基于HTTP协议的RPC调用

服务的调用者向服务的提供者发送请求,这种请求的方式可能是 GET、POST、PUT、DELETE 等中的一种,服务的提供者可能会根据不同的请求方式做出不同的处理,或者某个方法只允许某种请求方式。 而调用的具体方法则是根据 URL 进行方法调用,而方法所需要的参数可能是对服务调用方传输过去的 XML 数据或者 JSON 数据解析后的结果,返回 JOSN 或者 XML 的数据结果。

两种方式对比

  • 基于TCP的协议实现的RPC调用,由于 TCP 协议处于协议栈的下层,能够更加灵活地对协议字段进行定制,减少网络开销,提高性能,实现更大的吞吐量和并发数。但是需要更多关注底层复杂的细节,实现的代价更高。同时对不同平台,如安卓,iOS 等,需要重新开发出不同的工具包来进行请求发送和相应解析,工作量大,难以快速响应和满足用户需求。
  • 基于HTTP协议实现的RPC则可以使用 JSON 和 XML 格式的请求或响应数据。而 JSON 和 XML 作为通用的格式标准(使用 HTTP 协议也需要序列化和反序列化,不过这不是该协议下关心的内容,成熟的 Web 程序已经做好了序列化内容),开源的解析工具已经相当成熟,在其上进行二次开发会非常便捷和简单。但是由于 HTTP 协议是上层协议,发送包含同等内容的信息,使用 HTTP 协议传输所占用的字节数会比使用 TCP 协议传输所占用的字节数更高。因此在同等网络下,通过 HTTP 协议传输相同内容,效率会比基于 TCP 协议的数据效率要低,信息传输所占用的时间也会更长,当然压缩数据,能够缩小这一差距。

4. RPC 和 Restful API 对比

面对对象不同:

  • RPC 更侧重于动作。
  • REST 的主体是资源。

RESTful 是面向资源的设计架构,但在系统中有很多对象不能抽象成资源,比如登录,修改密码等而 RPC 可以通过动作去操作资源。所以在操作的全面性上 RPC 大于 RESTful。

传输效率:

  • RPC 效率更高。RPC,使用自定义的 TCP 协议,可以让请求报文体积更小,或者使用 HTTP2 协议,也可以很好的减少报文的体积,提高传输效率。

复杂度:

  • RPC 实现复杂,流程繁琐。
  • REST 调用及测试都很方便。

RPC 实现需要实现编码,序列化,网络传输等。而 RESTful 不要关注这些,RESTful 实现更简单。

灵活性:

  • HTTP 相对更规范,更标准,更通用,无论哪种语言都支持 HTTP 协议。
  • RPC 可以实现跨语言调用,但整体灵活性不如 RESTful。

5. RPC要解决的问题

  • 协议
  • 序列化与反序列化
  • 网络通信
  • 服务发现
  • 健康检查
  • 路由策略
  • 负载均衡
  • 异常重试
  • 熔断限流
  • 优雅关闭
  • 优雅启动
  • 流量隔离

6. 参考资料

https://zhuanlan.zhihu.com/p/148044947

https://www.cnblogs.com/crazylqy/p/7995395.html

https://developer.51cto.com/art/201906/597963.htm

Edgar

Edgar
一个略懂Java的小菜比