赞
踩
分布式的本质就是多进程协作,共同完成任务。要协作自然免不了通信。分布式通信技术模块中分布式通信中的远程调用。
以电商购物平台为例,每一笔交易都涉及订单系统、支付系统及库存系统,假设三个系统分别部署在三台机器 A、B、C 中独立运行,订单交易流程如下所示:
“下单”和“订单状态更新”两个操作属于本地调用,而“支付”和“出货”这两个操作是通过本地的订单系统调用其他两个机器上的函数(方法)实现的,属于远程调用。
本地调用通常指的是,进程内函数之间的相互调用;而远程调用,是进程间函数的相互调用,是进程间通信 IPC(Inter-Process Communication)的一种方式。通过远程调用,一个进程可以看到其他进程的函数、方法等。
在分布式领域中,一个系统由很多服务组成,不同的服务由各自的进程单独负责。因此,远程调用在分布式通信中尤为重要。
根据进程是否部署在同一台机器上,远程调用可以分为如下两类:
RPC 中的不同进程是跨机器的,适用于分布式场景。
B/S ( Browser/Server,浏览器 / 服务器) 架构中,被调用方(服务器)有一个开放的接口,然后调用方(用户)通过 Browser 使用这个接口, 来间接调用被调用方相应的服务,从而实现远程调用。
比如,用户 A 在自己的电脑上通过浏览器查询北京今天的天气, 浏览器会将用户查询请求通过远程调用方式调用远程服务器相应的服务,然后为用户返回北京今天的天气预报。
但是 B/S 架构是基于 HTTP 协议实现的,每次调用接口时,都需要先进行 HTTP 请求。既繁琐又浪费时间,不适用于有低时延要求的大规模分布式系统,所以远程调用的实现大多采用更底层的网络通信协议。
两种常用的远程调用机制:远程过程调用 RPC(Remote Procedure Call) 和远程方法调用 RMI(Remote Method Invocation)。
RPC 是调用方采用参数传递的方式,通过调用本机器上的一个函数或方法, 去执行远程机器上的函数或方法(可以统称为服务),并返回结果。在整个过程中,RPC 会隐藏具体的通信细节。
如下图所示,以电商购物平台例子中的“支付”操作为例,展示 RPC 调用的完整流程:
从整个流程可以看出,机器 A 上的 Pay(Order)、 Client Stub 和网络调用之间的交互属于本地调用,机器 B 上的 Pay(Order)、Server Stub 和网络调用之间的交互也属于本地调用。而机器 A 和机器 B 之间的远程调用的核心是,发生在机器 A 上的网络调用和机器 B 上的网络调用。
RPC 的目的,是要将第 2 到第 8 步的几个过程封装起来,让用户看不到这些细节。 从用户的角度看,订单系统的进程只是做了一次普通的本地调用,然后就得到了结果。
订单系统进程并不需要知道底层是如何传输的,在用户眼里,远程过程调用和调用一次本地服务没什么不同。这就是 RPC 的核心。
RPC 与本地调用(进程内函数调用)的区别:
本地调用过程中,同一进程是共享内存空间的,用户可以通过{函数名 + 参数}直接进行函数调用。
在 RPC 中,由于不同进程内存空间无法共享,且涉及网络传输,所以不像本地调用那么简单。
RPC 与本地调用主要有三点不同:
第一个区别:调用 ID 和函数的映射。
在本地调用中,进程内可共享内存地址空间,因此程序可直接通过函数名来调用函数。而函数名的本质就是一个函数指针,可以看成函数在内存中的地址。比如,调用函数 f(),编译器会帮我们找到函数 f() 相应的内存地址。
在 RPC 中,只通过函数名是不行的,因为不同进程的地址空间是不一样的。 所以在 RPC 中,所有的函数必须要有一个调用 ID 来唯一标识。一个机器上运行的进程在做远程过程调用时,必须附上这个调用 ID。 在通信的两台机器间,分别维护一个函数与调用 ID 的映射表。两台机器维护的表中,相同的函数对应的调用 ID 必须保持一致。
当一台机器 A 上运行的进程 P 需要远程调用时,它就先查一下机器 A 维护的映射表,找出对应的调用 ID,然后把它传到另一台机器 B 上,机器 B 通过查看它维护的映射表,从而确定进程 P 需要调用的函数,然后执行对应的代码,最后将执行结果返回到进程 P。
第二个区别:序列化和反序列化。
调用方调用远程服务时,需要向被调用方传输调用 ID 和对应的函数参数。
在本地调用中,进程之间共享内存等,只需要把参数压到栈里,然后进程自己去栈里读取就行。
在 RPC 中,两个进程分布在不同的机器上,使用的是不同机器的内存, 因此不可能通过内存来传递参数。
而网络协议传输的内容是二进制流,无法直接传输参数的类型,需要调用方把参数先转成一个二进制流,传到被调用方后,被调用方再把二进制流转换成自己能读取的格式。 这个过程叫作序列化和反序列化。 被调用方返回的结果也需要有序列化和反序列化的过程,不然调用方无法获取到结果。
RPC 与本地调用相比,参数的传递需要进行序列化和反序列化操作。
第三个区别:网络传输协议。
要想序列化后的数据能在网络中顺利传输,还需要有相应的网络协议,比如 TCP、UDP 等,因此就需要有一个底层通信层。
调用方通过该通信层把调用 ID 和序列化后的参数传给被调用方,被调用方同样需要该通信层将序列化后的调用结果返回到调用方。
只要调用方和被调用方可以互传数据,就可以作为这个底层通信层。使用的网络协议可以有很多,只要能完成网络传输即可。大部分 RPC 框架采用的是 TCP 协议。
以 RPC 框架 Apache Dubbo 为例,首先必须得有服务的提供方和调用方。如下图所示,假设服务提供方 1~4 为调用方 1~4 提供服务,每个调用方都可以任意访问服务提供方。
当服务提供方和服务调用方越来越多时,服务调用关系会愈加复杂。假设服务提供方有 n 个, 服务调用方有 m 个,则调用关系可达 n*m,这会导致系统的通信量很大。在服务调用方和服务提供方之间增加一个服务注册中心, 这样调用方通过服务注册中心去访问提供方相应的服务,这个服务注册中心相当于服务调用方和提供方的中心枢纽。
Dubbo 就是在引入服务注册中心的基础上,又加入了监控中心组件(用来监控服务的调用情况,以方便进行服务治理),实现了一个 RPC 框架。如下图所示,Dubbo 的架构主要 包括 4 部分:
Dubbo 的工作流程如下:
RMI 是基于 Java 环境的应用编程接口,能够让本地 Java 虚拟机上运行的对象,像调用本地对象一样调用远程 Java 虚拟机上的对象。
RMI 是 RPC 的一种具体形式,其原理与 RPC 基本一致,唯一不同的是 RMI 是基于对象的,充分利用了面向对象的思想去实现整个过程,其本质就是一种基于对象的 RPC 实现。
RMI 的具体原理如下图所示:
RMI 的实现中,客户端的订单系统中的 Stub 是客户端的一个辅助对象,用于与服务端实现远程调用;服务端的支付系统中 Skeleton 是服务端的一个辅助对象,用于与客户端实现远程调用。
客户端订单系统的 Pay(Order) 调用本地 Stub 对象上的方法,Stub 打包调用信息,比如变量、方法名等,通过网络发送给服务端的 Skeleton 对象,Skeleton 对象将收到的包进行解析,然后调用服务端 Pay(Order) 系统中的相应对象和方法进行计算,计算结果又会以类似的方式返回给客户端。
RMI 与 PRC 最大的不同在于调用方式和返回结果的形式,RMI 通过对象作为远程接口来进行远程方法的调用,返回的结果也是对象形式,可以是 Java 对象类型,也可以是基本数据类型。 RMI 的典型实现框架有 EJB(Enterprise JavaBean,企业级 JavaBean)。
远程过程调用包括同步调用和异步调用两种:
同步调用和异步调用的区别是,是否等待被调用方执行完成并返回结果。
同步调用通常适用于需要关注被调用方计算结果的场景,比如用户查询天气预报,调用方需要直接返回结果;
异步调用通常适用于对响应效率要求高、但对结果正确性要求相对较低的场景,比如用户下发部署一个任务,但真正执行该任务需要进行资源匹配和调度、进程拉起等过程,时间比较长,如果用户进程阻塞在那里,会导致体验很差。
本地调用通常指的是同一台机器进程间函数的相互调用,而远程调用是指不同机器进程间函数的相互调用。
RPC 是指调用方通过参数传递的方式调用远程服务,并得到返回的结果。在整个过程中, RPC 会隐藏具体的通信细节,使调用方好像在调用本地函数或方法一样。
RMI 是一个基于 Java 环境的应用编程接口,能够让调用方 Java 虚拟机上运行的对象,像调用本地对象一样,调用其他机器 Java 虚拟机上的对象。RMI 是 RPC 的一种具体实现形式。
Dubbo 是一个代表性的 RPC 框架,服务提供方首先向注册中心注册自己提供的服务,调用方通过注册中心获取提供的相对应的服务地址列表,然后选择其中一个地址去调用相应的服务。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。