一、RPC的概念

RPC 的全称是 Remote Procedure Call,即远程过程调用。

RPC 是帮助我们屏蔽网络编程细节,实现调用远程方法就跟调用本地(同一个项目中的方法)一样的体验,我们不需要因为这个方法是远程调用就需要编写很多与业务无关的代码。

二、RPC的通信流程

rpc mock实现 rpc实现原理_负载均衡

rpc mock实现 rpc实现原理_负载均衡_02

2.1、建立连接

目前我们RPC通信过程中,建立的连接一般是TCP连接。

TCP 是面向连接的

TCP 提供可靠交付,无差错、不丢失、不重复、并且按序到达

TCP 是面向字节流的,发送时发的是一个流

TCP 是可以提供流量控制和拥塞控制的,既防止对端被压垮,也防止网络被压垮

rpc mock实现 rpc实现原理_RPC_03

rpc mock实现 rpc实现原理_rpc mock实现_04

rpc mock实现 rpc实现原理_负载均衡_05

2.2、数据传输

2.2.1、序列化

1、为什么要序列化?

网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。对象是不能直接在网络中传输的,所以我们需要提前把它转成可传输的二进制,这个过程我们一般叫做“序列化”

把二进制的消息体逆向还原成请求对象,这个过程我们称之为“反序列化”。

2、序列化的方式有哪些?

JDK 原生序列化:序列化具体的实现是由 ObjectOutputStream 完成的,而反序列化的具体实现是由 ObjectInputStream 完成的。

Hessian:Hessian 是动态类型、二进制、紧凑的,并且可跨语言移植的一种序列化框架。Hessian 协议要比 JDK、JSON 更加紧凑,性能上要比 JDK、JSON 序列化高效很多,而且生成的字节数也更小。

Protobuf:Protobuf 是 Google 公司内部的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持 Java、Python、C++、Go 等语言。Protobuf 使用的时候需要定义 IDL(Interface description language),然后使用不同语言的 IDL 编译器,生成序列化工具类,

Protobuf 它的优点是:

①.序列化后体积相比 JSON、Hessian 小很多;

②.IDL 能清晰地描述语义,所以足以帮助并保证应用程序之间的类型不会丢失,无需类似 XML 解析器;

③.序列化反序列化速度很快,不需要通过反射获取类型;

④.消息格式升级和兼容性不错,可以做到向后兼容。

2.2.2、协议

1、什么是协议?

网络通信在传输的过程中,就是一堆堆的二进制码,协议就是通信双方的约定。通过协议约定,才能知道每一次的请求是什么样的。

为了避免语义不一致的事情发生,我们就需要在发送请求的时候设定一个约定,然后在收到请求的时候按照这个设定的约定进行数据分割。这个约定语义的表达,就是我们所说的协议。

2、协议的作用是什么呢?

①、我们知道只有二进制才能在网络中传输,所以 RPC 请求在发送到网络中之前,他需要把方法调用的请求参数转成二进制;转成二进制后,写入本地 Socket 中,然后被网卡发送到网络设备中。

②、在传输过程中,RPC 并不会把请求参数的所有二进制数据整体一下子发送到对端机器上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包(合并的前提是同一个 TCP 连接上的数据),至于怎么拆分合并,这其中的细节会涉及到系统参数配置和 TCP 窗口大小。对于服务提供方应用来说,他会从 TCP 通道里面收到很多的二进制数据,那这时候怎么识别出哪些二进制是第一个请求的呢?

③、同理在 RPC 传输数据的时候,为了能准确地“断句”,我们也必须在应用发送请求的数据包里面加入“句号”,这样才能帮我们的接收方应用从数据流里面分割出正确的数据。这个数据包里面的句号就是消息的边界,用于标示请求数据的结束位置。举个具体例子,调用方发送 AB、CD、EF 3 个消息,如果没有边界的话,接收端就可能收到 ABCDEF 或者 ABC、DEF 这样的消息,这就会导致接收的语义跟发送的时候不一致了。

rpc mock实现 rpc实现原理_序列化_06

2.3、动态代理

Spring AOP中,有jdk动态代理,和cglib动态代理,都是给生成相应的代理,去执行业务逻辑。

rpc调用过程中也是如此,我们调用远程接口,就像调用本地的接口方法一样,调用就可以获取到相应的数据,这里面,rpc帮我们屏蔽掉了底层的实现细节,其实底层是动态代理帮我们处理了很多调用逻辑。

rpc mock实现 rpc实现原理_RPC_07

三、服务治理

3.1、服务发现

rpc mock实现 rpc实现原理_rpc mock实现_08

服务注册:在服务提供方启动的时候,将对外暴露的接口注册到注册中心之中,注册中心将这个服务节点的 IP 和接口保存下来。

服务订阅:在服务调用方启动的时候,去注册中心查找并订阅服务提供方的 IP,然后缓存到本地,并用于后续的远程调用。

rpc mock实现 rpc实现原理_rpc mock实现_09

3.2、心跳检测

为什么要有心跳检测?

目前我们线上的服务都是集群化部署,所以每次发请求前,RPC 框架会根据路由和负载均衡算法选择一个具体的 IP 地址。为了保证请求成功,我们就需要确保每次选择出来的 IP 对应的连接是健康的。那如何保证选择出来的服务是健康的呢?此时涉及到了服务的心跳检测机制

rpc mock实现 rpc实现原理_序列化_10

健康状态:建立连接成功,并且心跳探活也一直成功;

亚健康状态:建立连接成功,但是心跳请求连续失败;

死亡状态:建立连接失败。

定时去调用接口提供方,查看服务提供方是否正常

3.3、负载均衡

rpc mock实现 rpc实现原理_负载均衡_11

我们的服务是集群化部署的,调用方调用的时候通过负载均衡,将请求分发给这个集群下的每个服务节点,从而达到多个服务节点共同分担请求压力的目的。

负载均衡主要分为软负载和硬负载,软负载就是在一台或多台服务器上安装负载均衡的软件,如 LVS、Nginx 等,硬负载就是通过硬件设备来实现的负载均衡,如 F5 服务器等。

RPC 负载均衡策略一般包括随机权重、Hash、轮询

RPC负载均衡与WEB负载均衡的区别?

它与 Web 服务的负载均衡的不同之处在于:RPC 框架并不是依赖一个负载均衡设备或者负载均衡服务器来实现负载均衡的,而是由 RPC 框架本身实现的,服务调用者可以自主选择服务节点,发起服务调用。

3.4、路由策略

现在我们的服务都是集群化部署的,服务提供方会部署多台机器,调用方在调用的时候,会选择其中一台机器去进行请求。

这时候就会涉及到路由策略,如何去找到这一台机器,去请求

3.5、业务分组

rpc mock实现 rpc实现原理_java_12

rpc mock实现 rpc实现原理_RPC_13

为什么要进行服务分组?

如上图所示:图1是未经过服务分组,图2是经过服务分组的,在业务请求量不大的情况下,图1是可以解决问题的,但是某一个业务方的请求量突然增大的时候,会对业务提供方所有的服务造成影响,这时候可能会导致自身服务的负载立刻变高,不仅影响自身集群的服务,还会其他业务方也会产生影响。这时候如果有业务分组就会解决这个问题