远程调用
远程调用
本地调用通常指的是,进程内函数之间的相互调用;而远程调用,是进程间函数的相互调用,是进程间通信 IPC(Inter-Process Communication)的一种方式。
根据进程是否部署在一台机器上,远程调用可以分为以下两类:
- 本地过程调用(Local Procedure Call,LPC),是指运行在同一台机器上的进程之间 的互相通信,即在多进程操作系统中,运行的不同进程之间可以通过 LPC 进行函数调 用。
- 远程过程调用(Remote Procedure Call,RPC),是指不同机器中运行的进程之间的 相互通信,某一机器上运行的进程在不知道底层通信细节的情况下,就像访问本地服务 一样,去调用远程机器上的服务。
远程调用的原理
在B/S ( Browser/Server,浏览器 / 服务器) 架构中,被调用方(服务器)会有一个开放的接口,然后调用方(用户)会通过Browser使用这个接口,来间接的调用相应的服务。
但是,B/S 架构是基于 HTTP 协议实现的,每次调用接口时,都需要先进行 HTTP 请求,比较的耗时,不适合分布式中的远程调用。更为常用的是 远程过程调用 RPC(Remote Procedure Call) 和 **远程方法调用 RMI(Remote Method Invocation)**。
RPC
简单地说,RPC 就是调用方采用参数传递的方式,通过调用本机器上的一个函数或方法, 去执行远程机器上的函数或方法(可以统称为服务),并返回结果。在整个过程中,RPC 会隐藏具体的通信细节。一个支付流程的网络调用如下图所示:

其中,Stub 是用于转换 RPC 过程中在订单系统和支付系统所在机器之间传递的参数。说白了,客户端的Stub用于封装参数,而服务端的Stub就是为了将收到的封装后的参数解析出来。
不难看出,机器 A 上的 Pay(Order)、 Client Stub 和网络调用之间的交互属于本地调用,机器 B 上的 Pay(Order)、Server Stub 和网络调用之间的交互也属于本地调用。而机器 A 和机器 B 之间的远程调用的核心是,发生在机器 A 上的网络调用和机器 B 上的网络调用。
RPC的目的是,将第 2 到第 8 步的几个过程封装起来,让用户看不到这些细节。 从用户的角度看,订单系统的进程只是做了一次普通的本地调用,然后就得到了结果。
订单系统进程并不需要知道底层是如何传输的,在用户眼里,远程过程调用和调用一次本地服务没什么不同。这就是 RPC 的核心。
RPC与本地调用的区别
第一个区别是调用 ID 和函数的映射。在本地调用中,进程内可共享内存地址空间,因此程序可直接通过函数名来调用函数。函数名的本质是一个函数指针,可以看作函数在内存中的地址。而在远程调用中,只通过函数名却不行,因为不同进程的地址空间不一样,所以必须通过一个调用ID来标识,在远程调用时要附上ID。
调用方首先通过映射表,先找到调用函数的ID,传给被调用方。被调用方拿到ID后,通过ID找到具体被调用的函数。
第二个区别是,序列化和反序列化。在本地调用中,进程之间共享内存等,因此我们只需要把参数压到栈里,然后进程自己去栈 里读取就行。但是在 RPC 中,两个进程分布在不同的机器上,使用的是不同机器的内存, 因此不可能通过内存来传递参数。
而网络协议传输的内容是二进制流,无法直接传输参数的类型,因此这就需要调用方把参数 先转成一个二进制流,传到被调用方后,被调用方再把二进制流转换成自己能读取的格式。 这个过程,就叫作序列化和反序列化。
第三个区别是,网络传输协议。序列化和反序列化解决了调用方和被调用方之间的数据传输 格式问题,但要想序列化后的数据能在网络中顺利传输,还需要有相应的网络协议,比如 TCP、UDP 等,因此就需要有一个底层通信层。而大部分的RPC框架,采用的都是TCP协议。
RMI
RMI 是一个基于 Java 环境的应用编程接口,能够让本地 Java 虚拟机上运行的对象,像调用本地对象一样调用远程 Java 虚拟机上的对象。RMI可以说是RPC的一种具体形式,原理与RPC基本一致,不同的是RMI 是基于对象的,充分利用了面向对象的思想去实现整个过程,其本质就是一种基于对象的 RPC 实现。
RMI 与 PRC 最大的不同在于调用方式和返回结果的形式,RMI 通 过对象作为远程接口来进行远程方法的调用,返回的结果也是对象形式,可以是 Java 对象 类型,也可以是基本数据类型。
参考
《分布式技术原理与算法实战》