RPC(Remote Procedure Call)叫作远程过程调用,它是利用网络从远程计算机上请求服务,可以理解为把程序的一部分放在其他远程计算机上执行。通过网络通信将调用请求发送至远程计算机后,利用远程计算机的系统资源执行这部分程序,最终返回远程计算机上的执行结果。
PRC主要设计五个部分:
服务调用方、调用方的本地存根及其一个RPC通信包的实例存在于调用者的机器上;而服务提供方、服务提供方的存根及另一个RPC通信包的实例存在于被调用的机器上。
服务调用方也叫服务消费者,它的职责之一是提供需要调用的接口的全限定名和方法,调用方法的参数给调用端的本地存根;职责之二是从调用方的本地存根中接收执行结果。
服务提供方就是服务端,它的职责就是提供服务,执行接口实现的方法逻辑,也就是为服务提供方的本地存根提供方法的具体实现。
在远程调用中,对于Consumer发起的函数调用,Provider如何精准的直到自己应该执行哪个函数呢?
这就需要stub了。Stub的存在就是为了让远程调用像本地调用一样直接进行函数调用,无须关系地址空间隔离、函数不匹配等问题。
Stub的职责就是进行类型和参数转化。
本地存根分为服务调用方的本地存根和服务提供方的本地存根。
服务调用方的本地存根和服务消费者都属于Consumer端,它们存在于同一台机器上,服务调用放的本地存根会接收Consumer的函数调用,本地存根会解析函数调用的函数名、参数等信息,整理并且组装这些数据,然后将这些数据安装定义好的协议进行序列化,打包成可传输的消息,交给RPCRuntime(RPC通信者)。服务调用方的本地存根除了会处理服务消费者提供的方法、参数、方法参数类型等数据,还会处理服务提供方返回的结果,它会将RPCRuntime返回的数据反序列化成服务调用方所需要的数据结果并传递给服务消费方。
从服务消费方的角度来看,Stub隐藏了远程调用的实现细节,就像是远程服务的一个代理对象,可以让服务消费方感觉调用远程服务方法就像调用本地方法一样。
服务提供方的本地存根与服务提供方都属于Provider端,它们一起存在于同一台机器上。当Provider端的RPCRuntime收到请求包后,交由服务提供方的本地存根进行参数等数据的转化。服务提供方的本地存根会重新转换客户端传递的数据,以便在Provider端的机器上找到对应的函数,传递正确的参数数据,最终正确地执行真实函数的调用。等函数执行完成后,服务提供方会将执行结果返回给服务提供方的本地存根,由本地存根再将结果数据序列化、打包,最后交给RPCRuntime。服务提供方的本地存根与服务调用方的本地存根一样都是充当了翻译员的角色。
RPCRuntime负责数据包的重传,数据包的确认、数据包路由和加密等。
在Consumer端和Provider端都会有一个RPCRuntime实例,负责双方之间的通信,可靠地将存根船渡地数据包传输到另一端。
RPC调用过程可以分为四个阶段,分别是服务暴露过程、服务发现过程、服务引用过程和方法调用过程。
服务暴露又称服务导出,服务导出的叫法相对于服务暴露更加形象一些。服务暴露发生在Provider端。
根据服务是否暴露到远程可以分为两种,一种是服务只暴露到本地,另一种则是暴露到远程。
Provider端的应用服务信息包括Provider端的地址、端口、应用服务需要暴露的接口定义信息等。Provider 端除了会在应用服务启动的时候将服务信息注册到注册中心,还会与注册中心保持心跳保活如果Provider端某个节点异常下线,注册中心在一段时间的保活检查后,就会将该节点的信息从注册中心中移除,防止Consumer端把请求发送到该下线的节点上。因为业务迭代迅速,服务端的服务变动及上下线很频繁,通过注册中心管理服务的地址信息可以让客户端动态地感知服务变动,并且客户端不需要再显式地配置服务端地址,只要配置注册中心地址即可,而注册中心集群一般不会变动。注册中心的内容会在后续更新介绍。
服务发现的过程发生在(Consumer)端,服务发现的过程也就是寻址的过程,Consumer端如果要发起RPC调用,则需要先知道自己想要调用的应用服务有哪些服务提供者,也就是需要知道这些服务提供者的地址和端口。
服务发现的方式有两种,分别是直连式和注册中心式,对应的是Provider端的两种服务暴露方式。
服务引用的过程发生在服务发现之后,当Consumer端通过服务发现获取所有服务提供者的地址后,通过负载均衡策略选择其中一个服务提供著的节点进行服务引用。服务引用的过程就是与某一个服务节点建立连接,以及在Consumer端创建接口的代理的过程其中建立连接也就是两端的RPCRuntime 建立连接的过程。
当服务引用完成后,Consumer端与Provider端已经建立了连接,可以进行方法的调用。
(1)服务消费者以本地调用方式(即以接口的方式)调用服务,它会将需要调用的方法、参数类型、参数传递给服务消费方的本地存根。
(2)服务消费方的本地存根收到调用后,负责将方法、参数等数据组装成能够进行网络传输的消息体(将消息体对象序列化为二进制数据),并将该消息体传输给RPC通信者。
(3)Consumer 端的RPC通信者通过sockets 将消息发送到Provider端,由Provider端的RPC通信者接收。Provider端将收到的消息传递给服务提供方的本地存根。
(4)服务提供方的本地存根收到消息后将消息对象反序列化。
(5)服务提供方的本地存根根据反序列化的结果解析出服务调用的方法、参数类型、参数等信息,并调用服务提供方的服务。
(6)服务提供方执行对应的方法后,将执行结果返回给服务提供方的本地存根。
(7)服务提供方的本地存根将返回结果序列化,并且打包成可传输的消息体,传递给Provider端的RPC通信者。
(8) Provider端的RPC通信者通过sockets将消息发送到Consumer端,由Consumer端的RPC通信者接收。Consumer端将收到的消息传递给服务消费方的本地存根。
(9)服务消费方的本地存根收到消息后将消息对象反序列化。反序列化出来的是方法执行的结果,并将结果传递给服务消费者。
(10)服务消费者得到最终执行结果。
服务暴露、服务发现、服务引用和方法调用这四个阶段组成了整个RPC的执行过程。
下面举一个例子来将这四个阶段连接起来。
现在有一个订单服务应用,其中订单服务提供了查询订单等方法。现在需要将该订单服务部署在三个节点上,分别是节点A、节点B和节点C。除了订单服务,还有一个用户服务,用户服务内的一个查询订单的功能需要调用订单服务的查询订单方法。所以这里的订单服务就是服务提供方,而用户服务就是服务消费方。订单服务每在一个节点上完成部署后,该节点的信息都会被注册到注册中心。当用户服务启动时,知道该服务依赖订单服务,所以会先从注册中心执行服务发现的过程,发现订单服务有三个节点提供服务,选择其中一个节点后,与该节点建立连接。当用户发起查看订单的请求时,用户服务会向该节点发送需要调用的方法信息,也就是查询订单方法的方法名、参数类型、参数等。等订单服务执行完成后将结果返回给用户服务,这个过程中订单服务与用户服务之间的连接一直保持活跃。当订单服务的该节点下线时,注册中心通知用户服务该节点已下线,当下次用户服务又发起对订单服务的调用时会选择另一个节点建立连接,并且发送调用请求,这就是远程过程调用的全部过程。
参考资料:《深入理解RPC框架原理与实现》 华钟明著.