RPC深入

作者:淡季的风
链接:https://www.jianshu.com/p/cde143836d0a
来源:简书

 

=====================================================

 

1.RPC简介

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

Http协议以其中的Restful规范为代表, 可读性好,且得到防火墙的支持、跨语言的支持。 缺点是Http协议有用信息占比太少, 效率低, 包含了大量的Http头信息。

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

  • 屏蔽远程调用和本地调用的区别, 让我们感觉就像是调用本地的方法。
  • 屏蔽网络通信的复杂性, 更加专注于业务层。

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

 

 


2、RPC核心功能

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

 

 


要让网络通信对使用者透明, 就需要对网络通信细节进行封装, 一个RPC通信的流程如下:

 

 


具体可参考这里:RPC入门

RPC调用的核心的组成部分:

  • Client: 负责发起请求调用。
  • Client stub: 存放服务端地址信息,负责将请求内容进行编码打包成网络消息,再通过网络传输发送给服务端。
  • Server stub: 接收客户端发送来的消息并进行解码,然后再调用本地服务处理。
  • Sever: 服务的真正提供者。
  • Network Service: 底层传输, 可以是TCP或者HTTP。

2.1、 RPC中的通信流程

RPC分为服务提供方和服务调用方。 服务方和调用方之间需要通过传递消息来进行通信:
1)服务调用方都先需要把请求转发给本地代理;
2)本地代理将请求数据进行序列化转换为二进制数据并按照RPC协议进行编码并通过网络发送给服务提供方;
3)服务提供方接收到网络上传输的的RPC请求后先按照RPC协议进行解码;
4)服务提供方将解码后的二进制数据进行反序列化并调用本地服务实现方去执行;
5)服务方将执行结果序列化并按照RPC协议编码发送给服务调用方。
6)服务调用方收到请求进行解码和反序列化就可以得到远程执行结果。

 

 


2.2、RPC中的网络传输

远程调用往往发生在网络上, 客户端和服务端通过网络连接的。消息数据结构被序列化为二进制后,就需要进行网络通信了。所有的数据都需要通过网络进行传输, 因此需要一个网络传输层。

在RPC可选的网络传输中有多种方式, 比如TCP、UDP、HTTP协议等。

每种协议都有自己的优缺点, 比较流行的几种开源RPC框架使用的协议也各有不同, 如Dubbo、Thrift等通过传输层TCP协议来完成网络传输, 而gRPC则通过应用层HTTP 2.0协议进行网络传输。

  • 基于TCP协议的RPC调用
    由服务的服务方和服务的调用方建立tcp连接,由服务的调用方将请求内容序列化后并按照RPC协议进行编码(本质上就是基于tcp私有协议报文的组装)通过网络发送给服务提供方, 服务提供方调用本地服务获得结果后再通过同样的方式把结果发送给服务调用方。
  • 基于HTTP协议的RPC调用
    由服务的调用方向服务的提供者发送HTTP请求, 这种请求可以是HTTP方法中的一种, 如GET、POST等。服务的提供者可以通过不同的调用方式做出不同的处理, 或者某个方法只允许一种某种请求方式。(这样说, web也是RPC的一种形式吗?)

基于TCP协议和基于HTTP协议的RPC服务的区别:

  • 基于TCP协议的RPC调用
    优点:由于TCP协议处于OSI七层模型的下层, 能够更加灵活的对协议字段进行定制、压缩、减少网络开销等, 这样可以提高性能,实现更大的吞吐量和并发数。
    缺点:需要更多关注底层的细节、实现的代价更高、适应不同的平台, 造成工作量大、难以快速响应用户需求、使用成本高等特点; 同时由于采用私有协议, 对于生态的推广也存在较大的问题。

  • 基于HTTP协议的RPC调用
    缺点
    1)由于HTTP协议为OSI七层模型的上层, 需要层层封装报文, 所以HTTP协议所占用的字节数会比使用TCP协议所占用的字节数要多, 同等情况下, 传输效率低;
    2) 其次HTTP1.0和HTTP1.1是文本协议, 而TCP协议是二进制协议,文本协议的开销更大, 占用字节数更多,也会造成同样的问题, 不过gRPC使用HTTP2.0协议,本身就是二进制协议。
    3)HTTP协议报文头中无用的内容太多, 导致有效信息占比低, 传输效率低下。
    优点:协议使用范围广, 上手简单, 推广性强。

2.3、 RPC中的网络IO选择

RPC网络通信过程中, 服务提供方和服务调用方需要建立连接,然而直接使用底层的socket成本比较高,IO阻塞且无法复用。这里就涉及到多种IO模型, 如阻塞IO、非阻塞IO、信号驱动IO、IO多路复用、异步IO等。

IO多路复用更加适合高并发的场景, 可以用较少的线程处理较多的IO请求。 RPC调用在大多数的情况下都是一个高并发的场景, 考虑到系统内核的支持、编程语言的支持、IO模型的特点等, 一般都会选择IO多路复用作为RPC通信的IO模型。

基于Java语言的RPC框架IO首选Netty, JDK自带的NIO框架支持不太友好, Netty是基于Reactor 模式实现的NIO框架, 支持完全零拷贝,对于RPC框架整体性能的提升作用非常大。

2.4、 RPC中的网络协议

从RPC的核心流程中可以看到, RPC的通信过程中需要对通信内容按照RPC协议编码解码。在第2.2小节中我们讲到, RPC的网络通信会使用诸如TCP、UDP、HTTP等协议, 但是这些协议更多的是通信的传输部分,对上层应用是透明的, 在使用RPC过程中并不需要太多关注这方面细节。

这里讲的RPC协议是指应用层的网络协议。举个例子: RPC 在通信过程中, 二进制数据通过网络进行传输, 但是在传输过程中RPC并不会一下子把所有的二进制报文通过网络都发送给另一端,中间可能会拆成多个数据组, 也可能会把多个报文合并发送, 这个中间如何识别每个请求报文的边界, 如何保证接收方和发送方的语义一致,就需要定义协议来规定报文的细节。

RPC协议需要规定协议长度、协议标示、消息ID、消息类型等内容,因此我们可以将协议定义为如下这种格式:

 

 

魔术位代表是什么协议,如REDIS、JDBC等等。

设计RPC协议需要考虑到可扩展、向下兼容, 并且尽可能的减少资源损耗。 协议好比定义符号、规则, 需要兼具功能性和可扩展性, 这样才能更好的提供服务。

2.5、RPC中的序列化和反序列化

序列化可以简单理解为对象->二进制数据, 反序列化则是相反的过程。



在本地调用中,我们只需要把参数压入到栈中, 让栈去调用即可。但是在远程调用中, 客户端和服务端在不同的进程中, 不能通过内存来传递参数, 甚至跨不同的语言。这个时候就需要客户端先把请求序列化为二进制数据, 服务端读取二进制数据在转化为能读取的格式。

 

 

常用的序列化方式:

  • Java原生的序列化: 仅支持java语言、性能较差。
  • JSON: 天生跨语言、扩展性强, 但是额外空间开销比较大。
  • Hession: 动态类型、二进制且紧凑, 可跨语言移植。
  • Protobuf: 需要定义IDL, 序列化后体积更小, 序列化速度快且扩展性强, 支持跨语言等特性。

除过以上几种, 还有诸如Thrift、Avro等很多序列化框架。每种序列化框架都有自己的优点和缺点, 它们在设计之初有自己独特的应用场景。

从RPC的角度来讲, 主要看以下几点:

  • 通用性: 是否支持Map等复杂的数据结构。
  • 性能: 包括时间复杂度和空间复杂度, 由于RPC框架将会被公司所有服务使用, 如果序列化上节约一点时间, 那整个公司的收益将非常可观。
  • 可扩展性:如果序列化协议有良好的可扩展性、支持自动增加新的字段、而不影响老的服务,将大大提高系统的灵活度。

2.6、 RPC中的代理

RPC中的代理负责远程接口的代理实现,代理发生在客服端、服务端, RPC框架需要解决:像本地调用一样调用远程的接口。于是如何组装数据报文, 经过网络传输报文,屏蔽远程接口的细节, 便是动态代理需要做的工作。

RPC会自动给接口生成一个代理类, 当我们在项目中注入初始化接口的时候, 运行过程中实际绑定的是这个接口的代理类, 在接口方法中被调用的时候, 它实际上是被生成代理类拦截到了, 这样就可以在生成的代理类中, 添加一些其他的执行逻辑。

为什么要加入动态代理?

  • 如果没有动态代理, 服务端大量的接口将不便于管理, 需要大量的if判断, 如果扩展了新的接口, 需要更改调用萝莉, 不利于扩展维护。

  • 动态代理可以做到内部方法级拦截, 并且添加其他额外功能, 必须负载管理,鉴权,日志埋点等。

动态代理调用过程

 

 

 

Java中动态代理的实现

  • jdk动态代理
  • cglib 动态代理
  • javassist 动态代理
  • asm 字节码

不同的动态代理实现各有千秋, 其中cglib底层依赖于asm字节码, javassist自成一派。 由于cglib和javassist需要直接操作字节码, 使用门槛比较高,但实际上它们的应用非常广泛, Spring就使用了jdk动态代理和cglib。

RPC框架无论使用何种动态代理技术, 所需要完成的任务也是固定的, 不外乎:组装报文、网络寻址、网络传输、序列化、反序列化、返回结果、埋点监控等等。

3、RPC的架构设计

RPC的整体架构设计如下所示, 除过RPC核心功能里面包括的序列化、协议编码、网络传输等部分外, 还包含服务发布中的注册中心、服务发现、负载均衡、配置管理等等:

 

 

 

 

 

3.1、RPC中的服务发现

成熟的RPC框架中, 布置客户端和服务端2个角色

posted @ 2022-05-16 11:28  larybird  阅读(59)  评论(0编辑  收藏  举报