一文带你了解RPC框架和实现原理

  • Post author:
  • Post category:其他




一、介绍


RPC



Remote Procedure Call

):远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。


RPC

是一种技术思想而非一种规范或协议,常见

RPC

技术和框架有:

  • 应用级的服务框架:阿里的

    Dubbo/Dubbox



    Google gRPC



    Spring Boot/Spring Cloud

  • 远程通信协议:

    RMI



    Socket



    SOAP(HTTP XML)



    REST(HTTP JSON)

  • 通信框架:

    MINA



    Netty

下面重点介绍三种:


  • gRPC

    :是

    Google

    公布的开源软件,基于最新的

    HTTP 2.0

    协议,并支持常见的众多编程语言。

    RPC

    框架是基于

    HTTP

    协议实现的,底层使用到了

    Netty

    框架的支持。

  • Thrift

    :是

    Facebook

    的开源

    RPC

    框架,主要是一个跨语言的服务开发框架。 用户只要在其之上进行二次开发就行,应用对于底层的

    RPC

    通讯等都是透明的。不过这个对于用户来说需要学习特定领域语言这个特性,还是有一定成本的。

  • Dubbo

    :是阿里集团开源的一个极为出名的

    RPC

    框架,在很多互联网公司和企业应用中广泛使用。协议和序列化框架都可以插拔是极其鲜明的特色。



二、完整的

RPC

框架

在一个典型

RPC

的使用场景中,包含了服务发现、负载、容错、网络传输、序列化等组件,其中“

RPC

协议”就指明了程序如何进行网络传输和序列化。

在这里插入图片描述



1、

RPC

核心功能


RPC

的核心功能是指实现一个

RPC

最重要的功能模块,就是上图中的”

RPC

协议”部分:

在这里插入图片描述



2、

RPC

组成部分

一个

RPC

的核心功能主要有

5

个部分组成,分别是:

客户端、客户端

Stub

、网络传输模块、服务端

Stub

、服务端等。


在这里插入图片描述

下面分别介绍核心

RPC

框架的重要组成:

  • 客户端(

    Client

    ):服务调用方。
  • 客户端存根(

    Client Stub

    ):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
  • 服务端存根(

    Server Stub

    ):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
  • 服务端(

    Server

    ):服务的真正提供者。

  • Network Service

    :底层传输,可以是

    TCP



    HTTP

如果网络传输中使用的是

HTTP

协议,建立

HTTP

协议之间有

TCP

三次握手,断开

HTTP

协议时有

TCP

四次挥手:

在这里插入图片描述



3、详细调用过程


RPC

实现流程和分工角色可以用下图来表示:

在这里插入图片描述

一次

RPC

调用流程如下:

  • 服务消费者(

    Client

    客户端)通过本地调用的方式调用服务。
  • 客户端存根(

    Client Stub

    )接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体。
  • 客户端存根(

    Client Stub

    )找到远程的服务地址,并且将消息通过网络发送给服务端。
  • 服务端存根(

    Server Stub

    )收到消息后进行解码(反序列化操作)。
  • 服务端存根(

    Server Stub

    )根据解码结果调用本地的服务进行相关处理
  • 服务端(

    Server

    )本地服务业务处理。
  • 处理结果返回给服务端存根(

    Server Stub

    )。
  • 服务端存根(

    Server Stub

    )序列化结果。
  • 服务端存根(

    Server Stub

    )将结果通过网络发送至消费方。
  • 客户端存根(

    Client Stub

    )接收到消息,并进行解码(反序列化)。
  • 服务消费方得到最终结果。



4、

RPC

核心之功能实现


RPC

的核心功能主要由

5

个模块组成,如果想要自己实现一个

RPC

,最简单的方式要实现三个技术点,分别是:

  • 服务寻址
  • 数据流的序列化和反序列化
  • 网络传输



4.1、服务寻址

服务寻址可以使用

Call ID

映射。在本地调用中,函数体是直接通过函数指针来指定的,但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。

所以在

RPC

中,所有的函数都必须有自己的一个

ID

。这个

ID

在所有进程中都是唯一确定的。

客户端在做远程过程调用时,必须附上这个

ID

。然后我们还需要在客户端和服务端分别维护一个函数和

Call ID

的对应表。

当客户端需要进行远程调用时,它就查一下这个表,找出相应的

Call ID

,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。

实现方式:服务注册中心。

要调用服务,首先你需要一个服务注册中心去查询对方服务都有哪些实例。

Dubbo

的服务注册中心是可以配置的,官方推荐使用

Zookeeper

实现案例:

RMI



Remote Method Invocation

,远程方法调用)也就是

RPC

本身的实现方式。

在这里插入图片描述

Registry(服务发现):借助

JNDI

发布并调用了

RMI

服务。实际上,

JNDI

就是一个注册表,服务端将服务对象放入到注册表中,客户端从注册表中获取服务对象。


RMI

服务在服务端实现之后需要注册到

RMI Server

上,然后客户端从指定的

RMI

地址上

Lookup

服务,调用该服务对应的方法即可完成远程方法调用。


Registry

是个很重要的功能,当服务端开发完服务之后,要对外暴露,如果没有服务注册,则客户端是无从调用的,即使服务端的服务就在那里。



4.2、序列化和反序列化

客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。

但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。

这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。

  • 对象转换为有序字节流的过程叫做序列化
  • 将二进制流转换成对象的过程叫做反序列化

这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。



4.3、网络传输

远程调用往往用在网络上,客户端和服务端是通过网络连接的。

所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把

Call ID

和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。

只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。

尽管大部分

RPC

框架都使用

TCP

协议,但其实

UDP

也可以,而

gRPC

干脆就用了

HTTP2

TCP 的连接是最常见的,简要分析基于 TCP 的连接:通常

TCP

连接可以是按需连接(需要调用的时候就先建立连接,调用结束后就立马断掉),也可以是长连接(客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制定期检测建立的连接是否存活有效),多个远程过程调用共享同一个连接。

所以,要实现一个

RPC

框架,只需要把以下三点实现了就基本完成了:


  • Call ID

    映射:可以直接使用函数字符串,也可以使用整数

    ID

    。映射表一般就是一个哈希表。
  • 序列化反序列化:可以自己写,也可以使用

    Protobuf

    或者

    FlatBuffers

    之类的。
  • 网络传输库:可以自己写

    Socket

    ,或者用

    Asio



    ZeroMQ



    Netty

    之类。



RPC

核心之网络传输协议

要实现一个

RPC

,需要选择网络传输的方式。

在这里插入图片描述



RPC

中可选的网络传输方式有多种,可以选择

TCP

协议、

UDP

协议、

HTTP

协议。

每一种协议对整体的性能和效率都有不同的影响,如何选择一个正确的网络传输协议呢?首先要搞明白各种传输协议在

RPC

中的工作方式。


基于

TCP

协议的

RPC

调用

由服务的调用方与服务的提供方建立

Socket

连接,并由服务的调用方通过

Socket

将需要调用的接口名称、方法名称和参数序列化后传递给服务的提供方,服务的提供方反序列化后再利用反射调用相关的方法。

最后将结果返回给服务的调用方,整个基于

TCP

协议的

RPC

调用大致如此。

但是在实例应用中则会进行一系列的封装,如

RMI

便是在

TCP

协议上传递可序列化的

Java

对象。


基于

HTTP

协议的

RPC

调用

该方法更像是访问网页一样,只是它的返回结果更加单一简单。

其大致流程为:由服务的调用者向服务的提供者发送请求,这种请求的方式可能是

GET



POST



PUT



DELETE

等中的一种,服务的提供者可能会根据不同的请求方式做出不同的处理,或者某个方法只允许某种请求方式。

而调用的具体方法则是根据

URL

进行方法调用,而方法所需要的参数可能是对服务调用方传输过去的 XML 数据或者

JSON

数据解析后的结果,最后返回

JOSN

或者

XML

的数据结果。

由于目前有很多开源的

Web

服务器,如

Tomcat

,所以其实现起来更加容易,就像做

Web

项目一样。


两种方式对比

基于

TCP

的协议实现的

RPC

调用,由于

TCP

协议处于协议栈的下层,能够更加灵活地对协议字段进行定制,减少网络开销,提高性能,实现更大的吞吐量和并发数。

但是需要更多关注底层复杂的细节,实现的代价更高。同时对不同平台,如安卓,

iOS

等,需要重新开发出不同的工具包来进行请求发送和相应解析,工作量大,难以快速响应和满足用户需求。

基于

HTTP

协议实现的

RPC

则可以使用

JSON



XML

格式的请求或响应数据。



JSON



XML

作为通用的格式标准(使用

HTTP

协议也需要序列化和反序列化,不过这不是该协议下关心的内容,成熟的

Web

程序已经做好了序列化内容),开源的解析工具已经相当成熟,在其上进行二次开发会非常便捷和简单。

但是由于

HTTP

协议是上层协议,发送包含同等内容的信息,使用

HTTP

协议传输所占用的字节数会比使用

TCP

协议传输所占用的字节数更高。

因此在同等网络下,通过

HTTP

协议传输相同内容,效率会比基于

TCP

协议的数据效率要低,信息传输所占用的时间也会更长,当然压缩数据,能够缩小这一差距。