2.1.3 三个基本问题

20世纪80年代中后期,惠普和Apollo提出了网络运算架构(Network Computing Architecture,NCA)的设想,并随后在DCE项目中将其发展成在UNIX系统下的远程服务调用框架DCE/RPC。笔者曾经在1.1节中介绍过DCE,这是历史上第一次对分布式的有组织的探索尝试,由于DCE本身是基于UNIX操作系统的,所以DCE/RPC通常也仅适合在UNIX系统程序之间使用。(微软COM/DCOM的前身MS RPC算是DCE的一种变体,把这些派生版算进去的话就要普适一些。)在1988年,Sun公司起草并向互联网工程任务组(Internet Engineering Task Force,IETF)提交了RFC 1050规范,此规范中设计了一套面向广域网或混合网络环境的、基于TCP/IP的、支持C语言的RPC协议,后被称为ONC RPC(Open Network Computing RPC,也被称为Sun RPC),这两套RPC协议就算是如今各种RPC协议和框架的鼻祖了,从它们开始,直至接下来这几十年所有流行过的RPC协议,都不外乎变着花样使用各种手段来解决以下三个基本问题。

1.如何表示数据

这里的数据包括传递给方法的参数以及方法执行后的返回值。无论是将参数传递给另外一个进程,还是从另外一个进程中取回执行结果,都涉及数据表示问题。对于进程内的方法调用,使用程序语言预置和程序员自定义的数据类型,就很容易解决数据表示问题;对于远程方法调用,则完全可能面临交互双方各自使用不同程序语言的情况,即使只支持一种程序语言的RPC协议,在不同硬件指令集、不同操作系统下,同样的数据类型也完全可能有不一样的表现细节,譬如数据宽度、字节序的差异等。有效的做法是将交互双方所涉及的数据转换为某种事先约定好的中立数据流格式来进行传输,将数据流转换回不同语言中对应的数据类型来使用。这个过程说起来拗口,但相信大家一定很熟悉,就是序列化与反序列化。每种RPC协议都应该要有对应的序列化协议,譬如:

·ONC RPC的外部数据表示(External Data Representation,XDR)

·CORBA的通用数据表示(Common Data Representation,CDR)

·Java RMI的Java对象序列化流协议(Java Object Serialization Stream Protocol)

·gRPC的Protocol Buffers

·Web Service的XML序列化

·众多轻量级RPC支持的JSON序列化

2.如何传递数据

如何传递数据,准确地说,是指如何通过网络,在两个服务的Endpoint之间相互操作、交换数据。这里“交换数据”通常指的是应用层协议,实际传输一般是基于TCP、UDP等标准的传输层协议来完成的。两个服务交互不是只扔个序列化数据流来表示参数和结果就行,许多在此之外的信息,譬如异常、超时、安全、认证、授权、事务等,都可能产生双方需要交换信息的需求。在计算机科学中,专门有一个名词“Wire Protocol”来表示这种两个Endpoint之间交换这类数据的行为,常见的Wire Protocol如下。

·Java RMI的Java远程消息交换协议(Java Remote Message Protocol,JRMP,也支持RMI-IIOP)

·CORBA的互联网ORB间协议(Internet Inter ORB Protocol,IIOP,是GIOP协议在IP协议上的实现版本)

·DDS的实时发布订阅协议(Real Time Publish Subscribe Protocol,RTPS)

·Web Service的简单对象访问协议(Simple Object Access Protocol,SOAP)

·如果要求足够简单,双方都是HTTP Endpoint,直接使用HTTP协议也是可以的(如JSON-RPC)

3.如何表示方法

确定表示方法在本地方法调用中并不是太大的问题,编译器或者解释器会根据语言规范,将调用的方法签名转换为进程空间中子过程入口位置的指针。不过一旦要考虑不同语言,事情又立刻麻烦起来,每种语言的方法签名都可能有差别,所以“如何表示同一个方法”“如何找到对应的方法”还是需要一个统一的跨语言的标准才行。这个标准可以非常简单,譬如直接给程序的每个方法都规定一个唯一的、在任何机器上都绝不重复的编号,调用时压根不管它是什么方法、签名是如何定义的,直接传这个编号就能找到对应的方法。这种听起既粗鲁又寒碜的办法,还真的就是DCE/RPC当初准备的解决方案。虽然最终DCE还是弄出了一套与语言无关的接口描述语言(Interface Description Language,IDL),成为此后许多RPC参考或依赖的基础(如CORBA的OMG IDL),但那个唯一的绝不重复的编码方案UUID(Universally Unique Identifier)也被保留且广为流传开来,并被广泛应用于程序开发的方方面面。类似地,用于表示方法的协议还有:

·Android的Android接口定义语言(Android Interface Definition Language,AIDL)

·CORBA的OMG接口定义语言(OMG Interface Definition Language,OMG IDL)

·Web Service的Web服务描述语言(Web Service Description Language,WSDL)

·JSON-RPC的JSON Web服务协议(JSON Web Service Protocol,JSON-WSP)

以上RPC中的三个基本问题,全部都可以在本地方法调用过程中找到对应的解决方案。RPC的设计始于本地方法调用,尽管早已不再追求实现与本地方法调用完全一致的目的,但其设计思路仍然带有本地方法调用的深刻烙印,抓住两者间的联系来类比,对我们更深刻地理解RPC的本质会很有帮助。