打赏

RPC系列:基本概念

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

RPC 是一种技术思想而非一种规范或协议,常见 RPC 技术和框架有:

  • 应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud、Facebook 的 Thrift、Twitter 的 Finagle 等。
  • 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
  • 通信框架:MINA 和 Netty。
  • ps: Google gRPC 框架是基于 HTTP2 协议实现的,底层使用到了 Netty 框架的支持。

1. RPC 框架

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

图 1:完整 RPC 架构图

2. RPC 核心功能

一个 RPC 的核心功能主要有 5 个部分组成,分别是:客户端、客户端 Stub、网络传输模块、服务端 Stub、服务端等。

图 4:RPC 核心功能图

下面分别介绍核心 RPC 框架的重要组成:

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

一次 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这些步骤都封装起来,让用户对这些细节透明。

3. RPC 核心功能实现技术点

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

3.1  透明化远程服务调用(代理)

  1. jdk 动态代理:更多使用动态代理
  2. 字节码生成:更为强大和高效,但代码维护不易

3.2  服务寻址(服务注册中心)

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

  Call ID 映射

/**
  1) 服务寻址可以使用 Call ID 映射。在本地调用中,函数体是直接通过函数指针来指定的,但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。

  2) 所以在 RPC 中,所有的函数都必须有自己的一个 ID。这个 ID 在所有进程中都是唯一确定的。

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

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

实现案例:RMI(Remote Method Invocation,远程方法调用)也就是 RPC 本身的实现方式。

图 9:RMI 架构图

Registry(服务发现):借助 JNDI 发布并调用了 RMI 服务。实际上,JNDI 就是一个注册表,服务端将服务对象放入到注册表中,客户端从注册表中获取服务对象。

3.3  编码与解码

客户端的请求消息结构一般需要包括以下内容:
/**
    1)接口名称:服务端就调用的那个接口;
    2)方法名:一个接口内可能有很多方法,如果不传方法名服务端也就不知道调用哪个方法;
    3)参数类型&参数值:参数类型有很多,比如有bool、int、long、double、string、map、list,甚至如struct(class);以及相应的参数值;
    4)超时时间
    5)requestID,标识唯一请求id
*/

服务端返回的消息结构一般包括以下内容。
/**
    1)返回值
    2)状态code
    3)requestID 
*/

3.4  序列化与反序列化

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

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

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

只有二进制数据才能在网络中传输,序列化和反序列化的定义是:

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

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

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

 

3.5 网络传输

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

所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把 Call ID 和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。

网络协议
    1. 尽管大部分 RPC 框架都使用 TCP 协议,但其实 UDP 也可以,而 gRPC 干脆就用了 HTTP2。
    2. 在 RPC 中可选的网络传输方式有多种,可以选择 TCP 协议、UDP 协议、HTTP 协议

网络框架
  可以自己写 Socket,或者用 Asio,ZeroMQ,Netty 之类。

网络通信
  目前有两种常用IO通信模型:1)BIO;2)NIO。一般RPC框架需要支持这两种IO模型。
  如何实现RPC的IO通信框架呢?
    1. 使用java nio方式自研,这种方式较为复杂,而且很有可能出现隐藏bug,但也见过一些互联网公司使用这种方式;
    2. 基于mina,mina在早几年比较火热,不过这些年版本更新缓慢;
    3. 基于netty,现在很多RPC框架都直接基于netty这一IO通信框架,省力又省心,比如阿里巴巴的HSF、dubbo,Twitter的finagle等。

 

基于 TCP 协议的 RPC 调用

/**
    由服务的调用方与服务的提供方建立 Socket 连接,并由服务的调用方通过 Socket 将需要调用的接口名称、方法名称和参数序列化后传递给服务的提供方,服务的提供方反序列化后再利用反射调用相关的方法。
    但是在实例应用中则会进行一系列的封装,如 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 协议的数据效率要低,信息传输所占用的时间也会更长,当然压缩数据,能够缩小这一差距。
*/

 

4. 摘录网址

  1. 花了一个星期,我终于把RPC框架整明白了!
  2. RPC原理及RPC实例分析

 

posted @ 2019-09-23 17:23  海米傻傻  阅读(1882)  评论(0编辑  收藏  举报