对RPC的一些理解


首先要知道,一台计算机的程序调用另一台计算机的子程序,通常有三种方法:RPC(远程过程调用)、RMI(远程方法调用)、HTTP。

本篇文章主要介绍RPC和RMI

RPC远程过程调用

什么是RPC(Remote Procedure Call) ?
一句话解释:RPC 能让我们像调用本地方法一样去调用部署在其他机器上的方法。

更具体的说:RPC 就是把拦截到的方法参数,转成可以在网络中传输的二进制数据,并保证在服务提供方能正确地还原出语义,最终实现像调用本地一样地调用远程方法。
RPC本质上不算是协议,而是一种调用方式,而像gRPC和thrift这样的具体实现,才是协议,它们是实现了RPC调用的协议

如何做到像调用本地方法一样去调用部署在其他机器上的方法?——动态代理
由服务提供者给出业务接口声明,在调用方的程序里面,RPC 框架根据调用的服务接口提前生成动态代理实现类,并通过依赖注入注入到声明了该接口的相关业务逻辑里面。该代理实现类会拦截所有的方法调用,在提供的方法处理逻辑里面完成一整套的远程调用,并把远程调用结果返回给调用方,这样调用方在调用远程方法的时候就获得了像调用本地接口一样的体验。

RPC调用流程

下图是极客时间《RPC 实战与核心原理》课程里的rpc通信流程:在这里插入图片描述
一次RPC调用的过程大致如下:

1.执行客户端调用语句,传送参数
2.调用本地系统发送网络消息
3.消息传送到远程主机
4.服务器得到消息并取得参数
5.根据调用请求以及参数执行远程过程(服务)
6.执行过程完毕,将结果返回服务器句柄
7.服务器句柄返回结果,调用远程主机的系统网络服务发送结果
8.消息传回本地主机
9.客户端句柄由本地主机的网络服务接收消息
10.客户端接收到调用语句返回的结果数据

RPC vs HTTP

微服务中用RPC取代HTTP的原因?
1、RPC性能比HTTP高
而微服务场景下是应用间的通信,对性能要求相对机器之间通信更高(http性能可以机器之间的通信,http性能不高的原因:1.无状态 2. 协议中加入很多无用的内容,比如换行符号、回车符等)
RPC性能高的原因:1、序列化之后可以进行压缩,2、网络传输采用IO多路复用

2、RPC比HTTP功能更强大
RPC封装了“服务发现”,“负载均衡”,“熔断降级”一类面向服务的高级特性,单纯使用http调用则缺少了这些特性。

rpc协议格式

可扩展协议:
在这里插入图片描述

自己设计一个灵活的RPC框架

我理解的架构设计呢,就是从顶层角度出发,厘清各模块组件之间数据交互的流程,让我们对系统有一个整体的宏观认识。
所以设计一个RPC框架需要首先知道RPC 里面都有哪些功能模块。

第一,RPC 本质上就是一个远程调用,那肯定就需要通过网络来传输数据。虽然传输协议可以有多种选择,但考虑到可靠性的话,我们一般默认采用 TCP 协议。为了屏蔽网络传输的复杂性,我们需要封装一个单独的数据传输模块用来收发二进制数据,这个单独模块我们可以叫做传输模块

第二,用户请求的时候是基于方法调用,方法出入参数都是对象数据,对象是肯定没法直接在网络中传输的,我们需要提前把它转成可传输的二进制,这就是我们说的序列化过程,对应序列化模块。
但只是把方法调用参数的二进制数据传输到服务提供方是不够的,我们需要在方法调用参数的二进制数据后面增加“断句”符号来分隔出不同的请求,在两个“断句”符号中间放的内容就是我们请求的二进制数据,这个过程我们叫做协议封装,对应协议模块。

第三,我们还可以在协议模块中加入压缩功能,对应数据压缩模块
因为在实际的网络传输过程中,我们的请求数据包在数据链路层可能会因为太大而被拆分成多个数据包进行传输,为了减少被拆分的次数,从而导致整个传输过程时间太长的问题,我们可以在 RPC 调用的时候这样操作:在方法调用参数或者返回值的二进制数据大于某个阈值的情况下,我们可以通过压缩框架进行无损压缩,然后在另外一端也用同样的压缩算法进行解压,保证数据可还原。

做完以上的基础核心工作之后,我们还希望能将RPC调用的细节对研发人员进行屏蔽,让他们感觉不到本地调用和远程调用的区别。
假设有用到 Spring 的话,我们希望 RPC 能让我们把一个 RPC 接口定义成一个 Spring Bean,并且这个 Bean 也会统一被 Spring Bean Factory 管理,可以在项目中通过 Spring 依赖注入的方式引用。这是 RPC 调用的入口,我们一般叫做 Bootstrap 模块

现在一个基本的RPC 框架就完成了,但是现在的RPC 框架只是为单机版本,因为它没有集群能力。
所谓集群能力,就是针对同一个接口有着多个服务提供者,但这多个服务提供者对于我们的调用方来说是透明的。
所以在 RPC 里面我们还需要给调用方找到所有的服务提供方,并需要在 RPC 里面维护好接口跟服务提供者地址的关系,这样调用方在发起请求的时候才能快速地找到对应的接收地址,这就是我们常说的服务发现,对应服务注册发现模块。

但服务发现只是解决了接口和服务提供方地址映射关系的查找问题,这更多是一种“静态数据”。
说它是静态数据是因为,对于我们的 RPC 来说,我们每次发送请求的时候都是需要用 TCP 连接的,相对服务提供方 IP 地址,TCP 连接状态是瞬息万变的,所以我们的 RPC 框架里面要有连接管理器去维护 TCP 连接的状态。对应连接管理模块

有了集群之后,提供方可能就需要管理好这些服务了,那我们的 RPC 就需要内置一些服务治理的功能,比如服务提供方权重的设置、调用授权等一些常规治理手段。对应服务治理模块

那到这儿,一个比较完善的 RPC 框架基本就完成了,功能也差不多就是这些了。按照分层设计的原则,我将这些功能模块分为了四层,具体内容见图示:
在这里插入图片描述

RPC 架构设计出来就完事了吗?当然不,技术迭代谁都躲不过。

举个例子,假如你设计了一个商品发布系统,早些年我们只能在网上购买电脑、衣服等实物商品,但现在发展成可以在网上购买电话充值卡、游戏点卡等虚拟商品,实物商品的发布流程是需要选择购买区域的,但虚拟商品并没有这一限制。如果你想要在一套发布系统里面同时完成实物和虚拟商品发布的话,你就只能在代码里面加入很多的 if else 判断逻辑,这样是能行,可整个代码就臃肿、杂乱了,后期也极难维护。

设计 RPC 框架也是一样的,我们不可能在开始时就面面俱到。那有没有更好的方式来解决这些问题呢?这就是我们接下来要讲的插件化架构。

在 RPC 框架里面,我们是怎么支持插件化架构的呢?我们可以将每个功能点抽象成一个接口,将这个接口作为插件的契约,然后把这个功能的接口与功能的实现分离,并提供接口的默认实现。在 Java 里面,JDK 有自带的 SPI(Service Provider Interface)服务发现机制,它可以动态地为某个接口寻找服务实现。使用 SPI 机制需要在 Classpath 下的 META-INF/services 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体实现类。

但在实际项目中,我们其实很少使用到 JDK 自带的 SPI 机制,首先它不能按需加载,ServiceLoader 加载某个接口实现类的时候,会遍历全部获取,也就是接口的实现类得全部载入并实例化一遍,会造成不必要的浪费。另外就是扩展如果依赖其它的扩展,那就做不到自动注入和装配,这就很难和其他框架集成,比如扩展里面依赖了一个 Spring Bean,原生的 Java SPI 就不支持。

加上了插件功能之后,我们的 RPC 框架就包含了两大核心体系——核心功能体系与插件体系,如下图所示:

在这里插入图片描述
这时,整个架构就变成了一个微内核架构,我们将每个功能点抽象成一个接口,将这个接口作为插件的契约,然后把这个功能的接口与功能的实现分离并提供接口的默认实现。

这样的架构相比之前的架构,有很多优势。
首先它的可扩展性很好,实现了开闭原则,用户可以非常方便地通过插件扩展实现自己的功能,而且不需要修改核心功能的本身;
其次就是保持了核心包的精简,依赖外部包少,这样可以有效减少开发人员引入 RPC 导致的包版本冲突问题。

RMI远程方法调用

RMI:远程方法调用(Remote Method Invocation)。能够让在客户端Java虚拟机上的对象像调用本地对象一样调用服务端Java虚拟机中的对象上的方法。

连接个数:多连接

连接方式:短连接

传输协议:TCP

传输方式:同步传输

序列化:Java标准二进制序列化

适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。

适用场景:常规远程服务方法调用,与原生RMI服务互操作 RMI协议采用JDK标准的java.rmi.*实现,采用阻塞式短连接和JDK标准序列化方式

一次RMI调用过程大致如下:

1.客户调用客户端辅助对象stub上的方法

2.客户端辅助对象stub打包调用信息(变量,方法名),通过网络发送给服务端辅助对象skeleton

3.服务端辅助对象skeleton将客户端辅助对象发送来的信息解包,找出真正被调用的方法以及该方法所在对象

4.调用真正服务对象上的真正方法,并将结果返回给服务端辅助对象skeleton

5.服务端辅助对象将结果打包,发送给客户端辅助对象stub

6.客户端辅助对象将返回值解包,返回给调用者

7.客户获得返回值

RPC vs RMI

RPC与RMI的区别

1:方法调用方式不同:

  • RMI中是通过在客户端的Stub对象作为远程接口进行远程方法的调用。每个远程方法都具有方法签名。如果一个方法在服务器上执行,但是没有相匹配的签名被添加到这个远程接口(stub)上,那么这个新方法就不能被RMI客户方所调用。
  • RPC中是通过网络服务协议向远程主机发送请求,请求包含了一个参数集和一个文本值,通常形成“classname.methodname(参数集)”的形式。RPC远程主机就去搜索与之相匹配的类和方法,找到后就执行方法并把结果编码,通过网络协议发回。

2:适用语言范围不同

RMI只用于Java;
RPC是网络服务协议,与操作系统和语言无关。

3:调用结果的返回形式不同

Java是面向对象的,所以RMI的调用结果可以是对象类型或者基本数据类型;RMI的结果统一由外部数据表示,这种语言抽象了字节序类和数据类型结构之间的差异。

参考

极客时间
RMI

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值