进程间通信(IPC)是在多任务操作系统或联网的计算机之间运行的程序和进程所采用的通信技术,IPC有两种类型的进程间通信的方式分别是本地过程调用(LPC)和远程过程调用(RPC)。
本地过程调用LPC
本地过程调用(LPC)用于多任务操作系统中,使同时运行的任务能够相互会话,任务共享内存空间以实现任务同步和互相发送消息。
例如:完成一个本地函数的调用
int add(int x, int y){
return x + y;
}
int a = 1;
int b = 2;
int result = add(a, b);
add()函数的执行流程
- 分别将变量a和b的值压入栈
- 执行add()函数,从栈中取出a和b的值,赋值给x和y。
- 计算x加y的值并保存到栈中
- 退出add()函数将x加y的值赋值给result
本地过程调用发生在同一进程中,共享内存区域。而RPC通信则需要跨域不同机器、不同进程,因此需要解决三个问题:函数ID(服务寻址)、数据流的序列化和反序列化、网络传输
如果add()函数调用在客户端,执行函数的函数体却在远程机器上,如何告知机器如何调用这个方法呢?
首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时需要检查一下函数以找到对应的ID,然后执行函数的代码。
客户端需要将本地参数传递给远程函数,本地调用的过程中直接压栈即可。但在远程调用过程中不在同一个内存中,无法直接传递参数的参数,因此需要客户端将参数转换为字节流,传递给服务器。服务器再将字节流转换为自身能读取的格式,这是一个序列化和反序列化的过程。
当数据准备好之后,如何进行传输呢?网络传输层需要将调用的ID和序列化后的参数传递给服务器,然后将计算好的结果序列化传递给客户端。因此TCP层可以完成上述操作。那么为什么不使用HTTP层呢?由于HTTP在应用层中完成,整个通信的代价比较高,RPC直接基于TCP进行远程调用,数据传输在传输层TCP完成,更加适合对效率要求比较高的场景。RPC主要依赖于客户端和服务器之间建立的Socket链接进行,但底层实现比REST更为复杂。
远程过程调用RPC
远程过程调用(RPC)类似于LPC,只是在网络中工作,RPC开始是出现在SUN微系统公司和HP公司运行的UNIX操作系统中。
RPC的概念与技术早在1981年由Neison提出,1984年Birrel和Neison将其用于支持异构型分布式系统的通讯。Birrell的RPC模型引入存根进程(stub)作为远程的本地代理,调用RPC运行时库来传递网络中的调用。Stub和RPC runtime屏蔽了网路所涉及的细节。特别是参数编码与转码以及网络任务的多样性。RPC作为网络通讯与委托计算的实现机制,在方法、协议、语义和实现上不断发展,种类繁多,其中SUN公司和开发软件基金会在分布式产品中所建立和使用的RPC较为典型。
RPC(Romote Procedure Call)远程过程调用,RPC是通信协议,该协议允许运行于一台计算机的程序调用另一台计算机的子程序,开发人员无需额外为交互作用编程。若软件采用面向对象编程,那么RPC又称为远程调用或远程方法调用。简单来说,远程调用协议是一种通过网络从远程计算机程序上请求服务,同时无需了解底层网络技术的协议。
RPC是一种用于构建基于客户端/服务器(C/S)的分布式应用程序技术,调用者与被调用者可能在同一台服务器上,也可能在由网络连接的不同服务器上。对客户端和服务器而言,使用RPC时网络通信是透明的,远程调用如何本地调用一样简单。简单来说,RPC就是要像调用本地函数一样去调用远程函数。
RPC解决了什么问题?
- 解决分布式系统中,服务之间的调用问题。
- 远程调用时,能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
为什么需要RPC?
- RPC可以用HTTP协议实现,HTTP是建立在TCP之上最广泛使用的RPC,互联网公司往往会使用自己的私有协议,比如腾讯的JCE协议,私有协议不具备通用性,但相比HTTP协议,RPC采用二进制字节码传输,更加高效也更为安全。
- 业界提倡的微服务中,服务之间通信方式中RPC是其中之一,RPC可以保证不同服务之间的相互调用,即使是跨语言跨平台也不是问题,让构建分布式系统更为容易。
- RPC框架都会存在服务降级、流量控制的功能,以保证服务的高可用。
RPC其实是一种技术思想而非规范或协议,常见RPC技术和框架有
- 应用级的服务框架:阿里的Dubbo/Dubbox、Google的gRPC、Spring Boot/Spring Clound
- 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)
- 通信框架:MINA、Netty
RPC核心
RPC核心功能是指实现一个RPC最重要的功能模块即RPC协议部分,一个RPC的核心功能主要由5部分组成分别是:客户端、客户端存根、网络传输模式、服务器、服务器存根。
组成 | 名称 | 描述 |
---|---|---|
客户端 | client | 服务调用方 |
客户端存根 | client stub | 存放服务器地址信息,将客户端请求参数数据打包成网络消息,在通过网路传输发送给服务器。 |
网络传输模式 | transfer | 底层传输,可以是TCP或HTTP。 |
服务器存根 | server stub | 接受客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。 |
服务器 | server | 服务提供者 |
RPC原理
一次客户端对服务器的远程过程调用,其内部操作可分为以下步骤:
- 客户端client(caller 服务调用者)
- 通过本地服务调用的方式调用远程服务
- 客户端存根 client stub
- 接收到调用请求后负责将方法和参数等信息序列化(组装)为能够在网络中传输的消息体。
- 调用客户端句柄,执行传送参数。
- 客户端存根 client stub
- 寻找到远程服务器地址并将消息通过网路发送给服务器
- 调用本地系统内核发送网络消息,消息传递到远程主机。
- 服务器存根 server stub
- 接收到客户端消息后进行解码即反序列化操作
- 服务器句柄得到消息并获取参数
- 服务器存根 server stub
- 根据解码结果调用本地服务进行处理
- 执行远程过程
- 服务器 server (callee 服务提供方)
- 本地业务处理
- 服务器 server
- 服务器处理结果返回给服务器存根
- 执行过程将结果返回给服务器句柄
- 服务器句柄返回结果并调用远程系统内核
- 消息传回本地主机
- 服务器存根 server stub
- 序列化结果
- 服务器存根 server stub
- 将序列化结果通过网络发送至客户端消费方
- 客户端存根 client stub
- 接收到服务器返回的消息并进行反序列化解码
- 客户端接收句柄并返回数据
- 客户端句柄由内核接收消息
- 客户端 client
- 服务器消费方获得最终结果
RPC技术点
一个典型的RPC使用场景中,包含服务发现、负载、容错、网络传输、序列化等组件,其中RPC协议指明了程序如何进行网络传输和序列化。
实现RPC重点需要实现三个技术,分别是:服务寻址、数据流的序列化和反序列化、网络传输
服务寻址
远程过程调用中包含三个角色的节点分别是服务调用方、服务提供方、注册中心
可靠的服务寻址方式主要是为了提供服务的发现,是RPC实现的基石。从服务提供方的角度来看,当提供方服务启动时需要自动向注册中心注册机器IP、端口和提供的服务列表,当提供方服务停止时需要向注册中心注销服务。提供方需定时向注册中心发送心跳,当一段时间未收到来自提供方的心跳后,会认为提供方已经停止服务,此时会从注册中心上摘掉对应的服务。服务调用方启动时会向注册中心获取服务提供方地址列表。
服务寻址可以使用Call ID映射,在本地调用中,函数体是直接通过函数指针来指定的,但在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以在RPC中所有的函数都必须具有自己的ID,这个ID在所有进程中都是唯一的。
客户端在做远程过程调用时,必须附上这个函数ID,还需要在客户端和服务端分别维护函数和Call ID的对应表。当客户端需要进行远程调用时,会差这个对应表找出对应的Call ID,然后将其传递给服务器,服务器也通过查对应表来确定客户端需要调用的函数,最终执行相对应函数的代码。
序列化和反序列化
远程过程调用最重要的是序列化与反序列化,因为传输的数据库必须是二进制的,因此客户端必须将数据序列化为二进制格式传递给服务器。为什么需要使用二进制,可直接使用文本吗?
采用原始的二进制可以省掉中间转换环节,而且数据量会大大减少,效率更高。采用文本方式则需要将整数转换为字符串后发送,服务端接收时又需要再次进行数据转换。
客户端如何将参数传递给远程的函数呢?在本地过程调用中,只需要将参数压入栈,让函数自己去栈中读取即可。但在远程过程调用中,客户端跟服务器是不同的进程,不能通过内存来传递参数。此时就需要客户端将参数先转换为字节流再传递给服务器,服务器接收后再将字节流转换为自己能读取的格式。而在网络中只有二进制数据才能传输,因此序列化与反序列化的定义也就是将对象转换成二进制流的过程是序列化,将二进制转换为对象的过程则是反序列化。
网络传输
远程调用往往使用在网络环境中,客户端和服务器是通过网络连接的,所有的数据都需要通过网络传输,因此需要一个网络传输层。网络传输层需要将Call ID和序列化后的参数字节流传递给服务器,然后再将序列化后的调用结果传回给客户端。只要能完成这两个操作,都可以作为传输层使用。因此它所使用的网络传输协议是不限的,只要能完成传输即可。尽管大部分RPC框架都是用TCP协议,其实UDP也可以。
TCP连接是最常见的,通常TCP连接可以是按需连接,即需要调用时先建立调用后断开。也可以是长连接,客户端和服务器建立连接后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制和定期检测建立的连接是否存活有效。另外,多个远程过程调用可以共享同一个连接。
简单来说,实现一个RCP框架只需要实现三点:
- Call ID映射:可以直接使用函数字符串,也可以使用整数ID,映射表通常是要一个哈希表。
- 序列化和反序列化:可使用Protobuf、FlatBuffers等之类
- 网络传输库:可使用Socket、Asio、ZeroMQ、Netty等
网络I/O模型(NIO)
客户端和服务器通信依赖Socket I/O,网络I/O模型可分为传统的阻塞I/O、非阻塞I/O、I/O多路复用、异步I/O。
RPC网络传输协议
在RPC中可选的网络传输方式有很多种,可选的包括TCP、UDP、HTTP,每种协议对整体的性能和效率都有不同的影响。如何选择一个正确的网络传输协议呢?
- 基于TCP的RPC
基于TCP协议的RPC调用是由服务调用方和服务提供方建立Socket连接,并由服务调用方通过Socket将需要调用的接口名称、方法名称、参数序列化后传递给服务提供方,服务提供方反序列化后再利用反射调用相关的方法。最后将结果返回给服务调用方,整个基于TCP协议的RPC调用大致如此。
- 基于HTTP的RPC
基于HTTP的RPC累加类似于访问网页,只是它返回的结果更为单一简单。首先由服务调用者向服务提供者发送请求,这种请求可能是GET、POST、PUT、DELETE等其中的一种,服务提供者可能会根据不同请求方式做出不同处理,或者某个方法只允许某种请求方式。调用的具体方法则根据URL进行方法调用,方法所需参数可能是对服务器调用方传输过去的XML或JSON数据解析后的结果,最后返回JSON或XML的数据结果。
两种方式对比
基于TCP实现的RPC由于TCP处于协议栈的下层,能够更加灵活地对协议字段进行定制,减少网路开销提高性能,实现更大的吞吐量和并发数。但需要更多关注底层复杂的细节,实现代价更高。同时对不同平台比如安卓、iOS等需要重新开发不同的工具包来进行请求发送和相应的解析,因此工作量大且难以快速响应和满足用户需求。
基于HTTP实现的RPC则可以使用JSON和XML格式的请求或响应数据,JSON和XML作为通用的格式开源解析工具已经相当成熟。但由于HTTP是上层协议,发送包含同等内容信息传输所占用的字节数会比TCP更高。
因此在同等网络下通过HTTP传输相同的内容效率会比TCP要低,信息传输所占的时间也会更长,虽然压缩数据能够减少这一差距。