RPC 简介
RPC 是一个远程调用,常用于业务系统之间的数据交互,需要通过网络来传输数据,因此通过建立在TCP之上保证传输的可靠性;
Remote Procedure Call --- 远程过程调用
- RPC 是解决进程间通信的一种方式,就是把拦截到的方法参数,转换成可以在网络中传输的二进制,并保证服务提供方可以正确的还原出语义,最终实现像调用本地一样调用远程的目的。
远程是指该方法不在当前进程中,而是在其他机器或进程中,综合来说就是可以利用进程间的通讯机制来调用和共享已经存在的功能数据,需要用到网络编程来实现RPC。- RPC 首先解决的是通讯的问题,主流的 RPC 框架是分别基于 HTTP 和 TCP 两种,其屏蔽了底层网络通信的复杂性,使得使用者更专注于业务逻辑方面。
- RPC可以理解为帮助我们屏蔽网络编程细节,实现调用远程方法就和调用本地方法一样的体验,不需要远程和本地的区别编写很多与业务无关的代码逻辑。
基于HTTP的RPC
基于HTTP的RPC调用很简单,和访问网页一样,只是返回的结果更单一(JSON或XML),其优点在于实现简单,标准化和跨语言,比较适合对外提供OpenAPI的场景,然而他的缺点就是HTTP协议传输效率低,短连接开销较大(HTTP2.0后有很大改进)。
基于TCP的RPC调用,由于TCP处于协议的下层,可以更加灵活的对协议字段进行定制,减少网络开销,提高性能,实现更大的吞吐量和并发量。但需要了解底层的复杂细节,跨语言跨平台难度大,适合于内部系统之间追求极高性能的场景。
RPC基本调用流程图流程图解读
- 调用法(Client)通过
本地的RPC代理调用对应的接口; - 本地代理将RPC的服务名、方法名和参数等信息替换成一个
标准的二进制形式,然后通过TCP通道传递给服务器(对象没有办法在网络中传输); - 服务器收到二进制数据后,将其
反序列成RPC Request对象; - 服务器根据反序列后的信息找到本地对应的方法,传入参数执行,得到结果后,将结果封装成RPC Response交给
RPC框架; - RPC框架通过RPC协议将收到的RPC Response对象
序列化成二进制形式,然后通过TCP通道传递回服务调用方(Client); - 调用方(Client)收到二进制数据后,将其
反序列化成RPC Response对象,并且将结果通过本地代理返回给业务方进行处理。 序列化与反序列化
序列化是将数据或对象转换成二进制数据传递给对方,比如在TCP通道里传递信息就是得序列化;
反序列化是将二进制数据转换成数据结构或对象的过程;
序列化和反序列化的规则就叫「协议」
协议简介
通常将数据格式的约定内容叫做
「协议」,如表明数据包的类型和长度信息等;大多数的「协议」分为数据头和消息头
- 数据头一般用于身份识别,包括协议识别、数据大小、请求类型、序列化等类型的消息;
- 消息头主要是请求的业务参数信息和拓展属性等
- 根据上述两部分,服务提供方就可以正确的从二进制数据中分割出不同的请求来(类似于高速路上的路段标志牌信息),同时根据请求类型和序列化内容类型,将二进制的消息体逆向还原成请求对象 -
反序列化
为什么服务器间的通信使用 RPC 而不是 HTTP
- HTTP 包冗余,体积过大,包含过多的无用 header;
HTTP 是面向文本的,因此在报文中的每一个字段都是一些ASCII码串,body 根据 Content-Type 有不同的编码。导致 HTTP 的包后很多冗余的部分,又要为了解析加入了很多诸如换行符回车符的无用内容。体积相比于发送同样信息的 RPC 多了许多。 HTTP是无状态的协议,客户端无法对请求和响应进行关联,每次请求都需要重新建立连接,响应完成再关闭连接,性能不高。
ASCII 是一种 7 位编码,但它存放时必须占全一个字节,也即占用 8 位。
RPC架构
其核心有Client、Server、Client Stub和Server Stub;其中后两个分别代表:
Client Stub客户端存根:存放服务端的地址信息,再将客户端的请求参数打包成网络消息,然后通过网络通道远程发送给服务端;
Server Stub服务端存根:接收客户端发送过来的消息,将消息解包,并调用本地方法;
RPC架构解析图
单纯的RPC调用还是不太友好的,因为对于研发人员来说要掌握太多的RPC底层细节,还需要去维护序列化和反序列化的逻辑,进行请求构造和网络调用等
利用相关技术简化封装RPC
参考AOP技术的实现,其核心就是
动态代理的技术,通过字节码增强对方法进行拦截,以便于增加需要额外处理的逻辑;
- 服务提供方逻辑:
- 给出业务接口声明
- RPC框架根据调用的服务接口提前生成动态代理实现类,并通过
依赖注入的技术注入到声明了该接口的相关业务逻辑里 - 该代理实现类会像AOP那样,拦截所有的方法调用,在提供的方法处理逻辑里完成一整套的远程调用,并将远程调用的结果返回给调用方,这样调用方在调用远程调用方法的时候就获得了调用本地接口一样的体验
- 调用方逻辑:
- 根据调用业务接口声明文档,像调用本地接口一样调用对应的方法,得到最后的结果
在应用的发展中,其架构都会向着“微服务化”的方向演变,整个应用都会被拆分成多个不同功能的应用,并部署在不同的服务器中,因此在不同服务器间的应用间的通信将变得尤为重要,常用的功能组件拆分思想为
分而治之
RPC应用场景分析
序列化是对方法调用的请求信息进行处理,解码器是对网络消息进行处理
- 分布式部署:
- RPC在系统间交互数据时使用RPC接口进行调用
- 通过时间管理的功能进行消息定向定点推送设备等信息
- 服务内部调用使用RPC,对外接口使用restful接口
- 服务内部如创建订单的流程、订单中心调度业务系统的订单信息
- RPC通过网络进行调用,存在超时的情况,合理配置超时时间和重试次数也很重要,同时会考虑配置降级处理进行边缘化问题处理
- 在RPC通信的内部,假如超时时间太短,内部通过了,但是调用者可能因为超时导致取消接口请求了已经,就会存在数据状态不一致的情况
- 在某些场景下,超时了就应该尽快的释放资源,不用再进行超时处理了,如大型的游戏化场景
RPC协议分类
- 通讯层协议:
通讯层协议一般负责将数据打包后,安全、完整的传输给接收方,HSF、Dubbo和gRPC这些都是通讯层协议;
通讯层协议一旦确定后就很少变化,需要具备足够好的通用性和拓展性;
- 应用层协议:
应用层协议是约定业务数据和二进制数据的转换规则,常见的应用层协议有Hessian、Protobuf、JSON;
应用层协议可以有具体业务进行自由选择,需要关注的是编码的效率和跨语言的特性; RPC协议基本构成 通常由一个Header和一个Payload(类似于HTTP的body)组成,合起来叫一个包(packet),之所以要有包,是因为二进制只完成Stream的传输,并不知道一次数据请求的起始和结束,需要预先定义好包结构才能解析; 协议设计类似于把一个数据包切分成若干单位长度的小盒子,然后约定小盒子里存储什么信息,一个小盒子就是一个Byte(协议中的最小单位),1Byte是8Bit,可以描述0~2^8个字节数,具体要使用多少个字节需要根据实际的存储信息。
设计一个协议需要考虑的点有很多,比如得考虑到后期的平滑升级、向下兼容等问题;存储信息也会随业务的不同而变化,比如需存储包的类型是请求还是相应、唯一的id进行相应与请求的关联、应用层协议的类型Codec、超时时间(请求头)、相应状态是成功还是失败(响应体)等信息。 蚂蚁最新的 RPC 通讯协议 Bolt精简协议构造