1、什么是RPC?
Wiki做出如下解释:
In distributed computing, a remote procedure call (RPC) is when a computer program causes a procedure (subroutine) to execute in a different address space (commonly on another computer on a shared network), which is coded as if it were a normal (local) procedure call, without the programmer explicitly coding the details for the remote interaction.
在分布式计算中,远程过程调用(RPC)是指计算机程序使一个过程(子例程)在不同的地址空间(通常在共享网络上的另一台计算机上)中执行,该地址空间的编码就好像它是一个普通的(本地)过程调用一样,而程序员没有显式地对远程交互的细节进行编码。
由此可以看出如下特征:
1、RPC的核心是调用者无感知地像调用本地方法一样对另一台机器上的方法进行调用。(当然不局限于方法,方法只是入口)
2、RPC是一种request-response形式,即请求-响应模式。
3、RPC是( inter-process communication)进程间通信(IPC)的一种形式,因为不同的进程有不同的地址空间:如果在同一台主机上,它们有不同的虚拟地址空间,即使物理地址空间是相同的;而如果它们在不同的主机上,则物理地址空间是不同的。许多不同的(通常不兼容的)技术被用来实现这个概念。
但是对于RPC的解释众说纷纭,或言其是OSI中的一个protocol,或言其为一种架构。我认为需要从广义和狭义两个角度理解它:
1、广义上RPC是一种Model即模式,类似于MVC,它的核心思想是处于不同物理和逻辑地址上的不同进程间的无感知调用。
2、狭义上RPC的确是一种Protocol,因为你可以基于其他更底层的OSI协议实现它,例如TCP、HTTP,但不止于此,RPC不止于通信协议,所以只能狭义上如此理解,且没有任何官方来publish RPC协议,这意味着你可以自定义属于你的RPC协议。(协议的本质是规范化)。
当然有人会说RFC中有对RPC的规范RFC 1831 - RPC: Remote Procedure Call Protocol Specification Version 2 (ietf.org),但这并不能说明它就是一个像HTTP或者FTP一样的协议。因为OSI所有协议的核心是对数据传输过程中由于不同分层产生的逻辑上的数据封装过程。
RFC(Request For Comments) 是由互联网工程任务组(IETF)发布的文件集。文件集中每个文件都有自己唯一编号,例如:rfc1831。目前RFC文件由互联网协会(Internet Society,ISOC)赞助发行。具体参考 :RFC(一系列以编号排定的文件)_百度百科 (baidu.com)
2、RPC的历史
1、请求-响应协议可以追溯到20世纪60年代末早期的分布式计算,远程过程调用作为网络操作模型的理论建议可以追溯到70年代,而实际实现可以追溯到80年代初。布鲁斯·杰伊·纳尔逊(brucejay Nelson)在1981年创造了“远程过程调用”这个词,人们普遍认为这是他的功劳。
2、现代操作系统中使用的远程过程调用可追溯到RC 4000多道程序设计系统,该系统使用请求-响应通信协议进行进程同步。将网络操作视为远程过程调用的想法至少可以追溯到20世纪70年代早期的ARPANET文档中。1978年,根据Brinch-Hansen提出的分布式进程,一种基于“外部请求”的分布式计算语言,由进程之间的过程调用组成。
3、最早的实用实现之一是Brian Randell及其同事于1982年在UNIX机器之间建立的纽卡斯尔连接。紧接着是Andrew Birrell和Bruce Nelson在Xerox PARC的Cedar环境中的“Lupine”。Lupine自动生成存根(Stub),提供类型安全绑定,Xerox在1981年以“Courier”的名义首次在商业上使用RPC,Sun的RPC(现在称为ONC-RPC)是第一个在Unix上流行的RPC实现,用作网络文件系统(NFS)的基础。
4、20世纪90年代,随着面向对象编程的普及,远程方法调用(RMI)的替代模型得到了广泛的实现,如在公共对象请求代理体系结构(CORBA,1991)和Java远程方法调用中。随着互联网的兴起,RMIs的受欢迎程度也随之下降,特别是在21世纪。
3、RPC的工作原理
其实RPC从模式上讲是一种请求-响应协议。RPC由客户端发起,客户端向已知的远程服务器发送请求消息,以使用提供的参数执行指定的过程。远程服务器向客户端发送响应,应用程序继续其进程。当服务器处理调用时,客户端将被阻止(除非客户端向服务器发送一个异步请求,例如XMLHttpRequest,否则它将等待服务器完成处理后再恢复执行)。在不同的实现中有许多变化和微妙之处,导致了各种不同(不兼容)的RPC协议。我认为不同的协议就不能称之为协议,都没有统一说话,谁和谁协议了?但它又是一种协议 因为的确通过其他OSI协议可以规范它。
远程过程调用和本地调用之间的一个重要区别是,由于不可预知的网络问题,远程调用可能会失败。而且,调用方通常必须在不知道是否实际调用了远程过程的情况下处理此类故障。幂等过程(如果多次调用则不会产生额外影响的过程)很容易处理,但仍然存在足够的困难,调用远程过程的代码通常仅限于精心编写的低级子系统。
而且对于RPC而言最麻烦的地方在于不同程序的实现方式不一样,有的是Java,有的是Go,有的是Ruby,数不清的语言等着它。在微服务分布式盛行的当下,RPC显然是集群系统间通信的最佳选择,光无感知就可以让你沉溺其中。真正的实现了服务间的解耦。
RPC具有如下流程:
1、客户机调用客户机存根。该调用是一个本地过程调用,以正常方式将参数推送到堆栈中。
2、客户机存根将参数打包到消息中,并进行系统调用以发送消息。打包参数称为编组。
3、客户端的本地操作系统将消息从客户端计算机发送到服务器计算机。
4、服务器机器上的本地操作系统将传入的数据包传递给服务器存根。
5、服务器存根从消息中解压参数。解包参数称为解组。
6、最后,服务器存根调用服务器过程。回复以相反的方向跟踪相同的步骤。
如图所示,无感知的实现在于网际通信和服务信息的相互存储以及调用过程中的对象序列化(传输数据)。这与微服务架构中的注册中心还有客户端以及服务端莫名的契合,其实这也是RPC发展在当下最为盛行的原因。注册中心解决了服务信息的相互发现。一众基于不同协议的网际通讯组件层出不穷,nio、netty。甚至是集成的RPC框架。gRPC,spring Cloud、 Dubbo。
4、RPC的实现方案对比
基于语言:
1、Java的Java远程方法调用(javarmi)API提供了与标准unixrpc方法类似的功能。
2、Go提供用于实现rpc的包rpc,并支持异步调用。
3、Modula-3的网络对象,它是Java的RMI的基础
4、RPyC在Python中实现RPC机制,并支持异步调用。
5、分布式Ruby(DRb)允许Ruby程序在同一台机器或网络上相互通信。DRb使用远程方法调用(RMI)在进程之间传递命令和数据。
6、Erlang是面向进程的,通过节点和本地进程之间的消息传递,本机支持分发和rpc。
7、Elixir构建在Erlang VM之上,通过代理和消息传递允许同一网络的进程通信(Elixir/Erlang进程,而不是OS进程)。
基于应用程序的:
1、动作消息格式(AMF)允许AdobeFlex应用程序与后端或其他支持AMF的应用程序通信。
2、远程函数调用是SAP系统之间通信的标准SAP接口。RFC调用要在远程系统中执行的函数。
通用的实现:
1、NFS(networkfilesystem)是RPC最主要的用户之一
2、开放网络计算远程过程调用,由Sun微系统公司开发
3、D-Bus开源IPC程序提供了与CORBA类似的功能。
4、SORCER为联邦方法调用提供了API和面向应用的语言(EOL)
5、XML-RPC是一种RPC协议,它使用XML对其调用进行编码,并使用HTTP作为传输机制。
6、JSON-RPC是一种使用JSON编码消息的RPC协议
7、JSON-WSP是一种使用JSON编码消息的RPC协议
8、SOAP是XML-RPC的继承者,还使用XML对其基于HTTP的调用进行编码。
9、ZeroC的互联网通信引擎(Ice)分布式计算平台。
10、用于构建网络服务的蚀刻框架。
11、Apache节俭协议和框架。
12、CORBA通过称为对象请求代理的中间层提供远程过程调用。
13、Libevent为创建RPC服务器和客户端提供了一个框架
14、Windows通信基础是.NETFramework中用于连接、面向服务的应用程序的应用程序编程接口。
15、Microsoft.NET远程处理为在Windows平台上实现的分布式系统提供RPC功能。它已被WCF取代。
16、Microsoft DCOM使用基于DCE/RPC的MSRPC
17、开放软件基础DCE/RPC分布式计算环境(也由微软实现)。
18、googleprotocolbuffers(protobufs)包包括一个用于其RPC协议的接口定义语言,作为gRPC于2015年开源
19、WAMP将RPC和Publish-Subscribe结合到一个单一的传输无关协议中。
20、GoogleWebToolkit使用异步RPC与服务器服务通信。
21、apacheavro提供了RPC,其中不需要在连接握手和代码生成中使用客户端和服务器交换模式。
22、嵌入式RPC是NXP开发的轻量级RPC实现,主要针对CortexM内核
23、KF可信执行环境使用代理和对象封送来跨沙盒通信对象
24、msgpack rpc是一个轻量级rpc实现,使用MessagePack序列化数据。它用于文本编辑器Neovim。
如此令人眼花缭乱的实现,可见RPC并不是一种协议,还是那句话如果是协议怎么会这么多不一样呢?谁跟谁协议了呢?
5、实现自己的RPC
既然如此让我们尝试实现自己的RPC。当聚焦RPC的工作原理时,我们发现如下几点是RPC实现的核心:
1、建立通信
首先要解决通讯的问题:即A机器想要调用B机器,首先得建立起通信连接。主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
通常这个连接可以是按需连接(需要调用的时候就先建立连接,调用结束后就立马断掉),也可以是长连接(客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制定期检测建立的连接是否存活有效),多个远程过程调用共享同一个连接。
2、服务寻址
要解决寻址的问题,也就是说,A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么。通常情况下我们需要提供B机器(主机名或IP地址)以及特定的端口,然后指定调用的方法或者函数的名称以及入参出参等信息,这样才能完成服务的一个调用。
可靠的寻址方式(主要是提供服务的发现)是RPC的实现基石,比如可以采用Dubbo或者Zookeeper来注册服务等等。
3、网络传输
3.1、序列化
当A机器上的应用发起一个RPC调用时,调用方法和其入参等信息需要通过底层的网络协议如TCP传输到B机器,由于网络协议是基于二进制的,所有我们传输的参数数据都需要先进行序列化(Serialize)或者编组(marshal)成二进制的形式才能在网络中进行传输。然后通过寻址操作和网络传输将序列化或者编组之后的二进制数据发送给B机器。
3.2、反序列化
当B机器接收到A机器的应用发来的请求之后,又需要对接收到的参数等信息进行反序列化操作(序列化的逆操作),即将二进制信息恢复为内存中的表达方式,然后再找到对应的方法(寻址的一部分)进行本地调用(一般是通过生成代理Proxy去调用,
通常会有JDK动态代理、CGLIB动态代理、Javassist生成字节码技术等),之后得到调用的返回值。
目前可以实现序列化的框架,当然不止这些,基于对象,基于xml,基于json,基于任何你需要的格式。
6、总结
1、RPC不是一种协议,也不是框架,RPC是一种模式,聚焦于无感知的远程程序调用。
2、很多大型互联网公司都有自己的RPC的实现。
3、RPC的核心是,服务信息的互相发现、序列化、网际通信。
4、显然服务集群间的通信协议更适合TCP或者HTTP2.0,序列化适合Hessian、Thrift。因为更具通用性更快(轻巧)。
5、你甚至可以定制自己的RPC协议,构建属于自己的RPC 模式。