《A survey of asynchronous remote procedure calls》论文翻译
#RPC #翻译 [字体 小·中·大]这篇 ACM 论文介绍了异步 RPC 的实现,在先在流行的 RPC 框架(如:Dubbo)都能看到这些技术的缩影,最经典的莫过于 Future 模式。
PDF 原文件: 链接
另外一篇经典的论文《Implementing remote procedure calls》在 ACM Digital Library 可以下载: 链接
1 介绍
远程过程调用(RPC)是分布式应用中一种简单、灵活、强大的进程间通讯(IPC)的方式[Wilbur and Bacarisse 87],它被广泛用在分布式系统和分布式应用的通讯,如 Amoeba 分布式操作系统、Sprite 网络操作系统[Ousterhout et al. 88]、Andrew 文件系统。
许多 RPC 系统已经在 Nelson 博士的理论发表后构建,其中值得注意的是 Cedar RPC 系统[Birrell and Nelson 84]、NCA/RPC 系统[Dineen et al. 87]和 HRPC 系统[Bershad et al. 87]。关于这些工作的一项调查表明,然而大部分的 RPC 系统通常使用同步的方式,因此未能完全利用分布式应用固有的并发特性,这严重影响了各种分布式应用程序之间的交互,结果是低效的。为了实现并发人们借助其他的方式,例如轻量级进程(线程)或者低级机器间消息传递(发送|接收),如果主机的操作系统,如 Unix 不支持线程,将不得不使用重量级进程代替,然而这两种解决办法都不吸引用户,第一种解决方式不灵活、很难 debug 且在大型分布式环境中不能扩展;第二种解决方式比使用 RPC 系统更困难。鉴于此,许多异步 RPC 系统已经被设计实现以获取更高的并发性能,同时也保持了之前熟悉和简单的同步 RPC。异步 RPC 在调用时不阻塞调用者(客户端)并且当调用者需要时可以接收回复的消息,因此期间允许客户端并行处理本地服务器调用。
这篇论文研究比较一些有特色的异步 RPC 系统,异步 RPC 系统的讨论包含:Athena Non-blocking RPC、NCA maybe RPC、Sun batching RPC、Remote Pipes、Stream(Promies)、Future 和 ASTRA。这些比较主要基于异步 RPC 系统的以下特点: (1)支持接收和回复消息 (2)传输层协议 (3)调用消息和返回消息的顺序 (4)调用语义 (5)优化低延迟和高吞吐 (6)优化机器内部调用
在部分 2 审查了异步 RPC 系统的发展诱因并讨论了 RPC 设计准则;在第 3 部分展示对每一种异步 RPC 系统的分析结果;第 4 部分展示每个异步 RPC 系统测试得分和讨论结果。
2 背景
异步 RPC 机制的设计主要是为了在保持 RPC 抽象的简单性和熟悉性的同时实现高并行性。通过为每个 RPC 调用创建多个轻量级进程(线程),可以实现有限的并行度[Bal 等人 87]。这允许客户端对多个服务器进行多次调用,并且仍然能够与服务器并行执行。程序结构类似于 fork | join,但很难调试。虽然将 RPC 与线程绑定会带来较少的开销,但该解决方案的可扩展性不好。在 RPC 调用数量动态增长和收缩的大型分布式环境中,由于线程创建、上下文切换和线程破坏的累积成本,使用线程并不经济。此外,某些主机操作系统(如 Unix System V)不支持线程。
一些系统,如 Multi-RPC[Satyanarayanan and Siegel 90]试图通过允许客户端同时调用一个过程的多个实例来实现更高的并行性。客户端将被阻塞,直到收到所有响应,或者客户端显式终止调用。虽然实现了一些并行性,但客户端不可能并行调用两个不同的过程。因此,在许多情况下,多 RPC 不能充分利用并行性。
或者可以通过使用机器间的消息传递 IPC 机制来实现所需的并行。然而,这样一个系统的用户必须处理许多以前隐藏在 RPC 中的细节,包括数据表示、消息打包,以及响应与请求消息的配对。异步 RPC 提供了消息传递 IPC 和同步 RPC 之间的中间抽象。
2.1 设计原则
在异步 RPC 系统的设计中,有几个标准是可取的。第一,异步 RPC 系统必须具有同步 RPC 系统的外观,但客户端在发出异步调用后不会等待回复。在这种情况下,客户可能无法延迟收到回复。此外,希望服务器按照客户机调用的顺序接收和执行所有调用,以保留正确的调用语义。因此,异步 RPC 系统应该保留传统同步 RPC 系统必须提供的所有优点,同时允许客户端和服务器并行执行。
第二,异步 RPC 系统必须设计为独立于传输,以满足不同类型的应用需求。通常,客户机和服务器涉及两种交互,间歇交换和扩展交换。所谓间歇式交换,我们指的是客户端对服务器进行几次偶尔的请求-响应(RR)类型的调用。我们对一个特定类型的服务器进行大量的数据交换。异步 RPC 系统应结合虚电路和数据报传输协议,以便允许应用程序选择满足其需求的最佳传输。为了获得最佳性能,可以选择虚拟电路进行扩展交换,因为它提供了更好的流量和错误控制,而处理开销可以忽略不计。另一方面,数据报由于其简单性更适合于间歇交换。
第三,必须为机器内调用优化异步 RPC 功能。根据 Bershad 等人进行的一项调查,[Bershad et al. 89]不到 10%的远程活动是跨机器通话。这是因为大多数应用程序都是为了最大化本地处理而设计的。鉴于此,应该设计一个异步 RPC 系统,通过绕过昂贵的数据转换和网络通信操作来优化机器内调用。
3 异步 RPC 系统
根据调用是否返回值,异步 RPC 调用可以分为两种类型。大多数异步 RPC 系统只支持不返回值的调用,很少有系统同时支持这两个类。本文讨论的异步 RPC 机制的分类如图 1 所示。
在本节中,我们将按照开发的时间顺序检查一些异步 RPC 系统,并强调它们的相似性和差异。第 3.1 节描述了不返回值的异步 RPC 系统。我们将在第 3.2 节中讨论可以延迟接收回复的异步 RPC 系统。
3.1 异步 RPC 的返回值
3.1.1 MIT 项目 Athena 非阻塞 RPC
麻省理工学院 Athena 项目[Champine et al. 90]的目标是为了教育目的整合各种计算和通信资源。Athena RPC [Souza and Miller 86]是在 Athena 项目的一致性模型 0 的约束下开发的。一些约束包括不修改 Unix 内核、在异构环境中支持 RPC 以及支持多种语言套件。它是在 BSIM.2 Unix 操作系统中作为原型实现的。 Athena RPC 同时提供阻塞(同步)和非阻塞(异步)调用。Athena 非阻塞 RPC 的开发主要是为了提高不需要从被调用过程返回任何信息或状态的应用程序的性能。 请求-响应(UDP-RR)协议建立在 UDP 之上,用于正常的阻塞 RPC。非阻塞 RPC 使用 UDP 作为其传输机制。因此,它不能保证通话信息的传递,也不能保证传递顺序。因此,服务器中的执行顺序可能与客户端调用的顺序不同。 Athena 非阻塞 RPC 可能具有调用语义,正如 Spector 的论文[Spector 85]中所定义的那样,因为不保证调用消息的传递。如果需要任何通信可靠性,应用程序必须实现自己的端到端机制,这种语义可能不适合任何事务性应用程序。 为了减少延迟,Athena 非阻塞 RPC 在每次调用后立即发送其调用消息,此外它不会区分机器间调用和机器内调用,因此不会对本地机器内调用进行优化。
3.1.2 NCA Maybe RPC
NCA/RPC [Zahn et al. 90]由 HP/Apollo 开发,作为网络计算体系结构(NCA)的一部分,它为程序员提供了一组丰富的 RPC 调用——发送-接收的普通阻塞 RPC——被称为 maybe RPC、broadcast RPC 的异步 RPC。
要指定 Maybe RPC,其接口定义语言(Interface Definition Language,IDL)文件必须在过程定义前面包含[maybe]操作属性,用下面网络接口定义语言(C 语法)编写的以下代码段说明了这一点:
1[maybe] void simple$op(...);
这里 simple$op()是不返回值的远程过程的名称。存根生成器将生成IDL文件中指定的存根过程simple$op()。客户端可以将远程过程 simple$op()作为本地过程调用。
除了上述调用类型之外,NCA/RPC 还提供了在其他 RPC 实现中很少可用的函数,例如客户端可以发送“ping”数据包来查询未完成的请求,并发送“quit”数据包来通知服务器它将中止远程调用的处理。
NCA/RPC 设计用于在无连接的传输层上工作。目前,该层支持 Apollo 域协议(DDS)和 UDP。NCA 可能 RPC 在特性上与 Athena 非阻塞 RPC 非常相似,两者都依赖于不可靠的数据报来传输调用消息,并且不希望服务器回复,因此它可能具有调用语义,并且消息的传递顺序无法保证。
NCA Maybe RPC 不尝试缓冲调用消息,而立即发送调用消息以实现低延迟,此外它不优化机器内调用,因为它不区分机器内调用和机器间调用。
3.1.3 Sun Batching RPC
Sun ONC/RPC[Sun 88]由 Sun Microsystems 开发,作为开放网络计算(ONC)的一部分。Sun Batching RPC 是 Sun RPC 提供的调用类型之一,其他的是普通的同步 RPC 和广播 RPC。Batching RPC 允许从客户端向服务器进行一系列调用,管道中的每个 RPC 调用都不需要服务器回复,服务器也无法发送回复消息,最后一个调用必须是正常的阻塞 RPC,以便清除调用管道。 Sun RPC 为应用程序程序员提供了两种类型的接口。其中一个可以作为库例程使用,另一个接口使用 RPC 规范语言(RPCL)和存根生成器(RPCGEN),RPCL 是外部数据表示(XDR)[Sun 87]规范的扩展,要使用批处理 RPC,可以使用 RPCGEN 或库例程。
clnt_call()库例程可用于调用 Sun 批处理 RPC,如下所示:
1clnt_call(client, PROCNUM, xdr req, &request, xdr_ret, return, timeout);
通过设置 timeout、xdr_ret 和无返回(NULL),该调用与正常的阻塞 RPC 不同。这里 client 是 clnt_create()返回的句柄,它创建并绑定一个 RPC 客户端句柄,PROCNUM 是要在服务器中调用的过程号,xdr req 是被称为请求的数据的相应 xdr 例程。
Sun RPC 提供 UDP 和 TCP 作为其传输通信机制,然而批处理 RPC 是建立在 TCP 之上的,与 Athena 非阻塞 RPC 和 NCA maybe RPC 相比,所有消息都是可靠传递的,TCP 是唯一受支持的传输机制,这一事实使得它不适合于请求-响应类型的事务性应用程序,Sun 批处理 RPC 的调用语义最多为一次,这是对 Athena 非阻塞 RPC 和 NCA maybe RPC 的可能调用语义的改进。
Sun Batching RPC 利用 TCP 缓冲调用消息,并在一次 Unix write()系统调用中将它们发送到服务器。这大大降低了系统调用开销,从而提高了性能和吞吐量。然而,在 Sun 批处理 RPC 中,没有对机器内调用进行优化。
3.1.4 远程管道
远程管道(Remote Pipe)[Gifford and Glasser 88]旨在以类型安全的方式高效传输批量数据和增量结果。这是通过一种称为信道模型的通信模型来实现的,通道模型由三个基本元素组成:远程过程、远程管道和通道组。
在信道模型中,节点类似于进程,一个节点可以包含多个通道,在这种情况下,通道要么是远程过程,要么是管道,远程过程和管道之间的区别在于前者是同步 RPC,而后者是不返回值的异步 RPC。一个节点可以导入任何其他节点导出的任何通道,也可以将它们重新导出到其他节点。这使得通道成为一级值,可以在节点之间自由交换[Nelson 81]。导入节点可以将通道分组为一个称为通道组的集合,频道组控制通话顺序,发送到信道组内信道的数据按发送顺序接收。
这里 yyy 是 xxx 类型的一个实例。
信道模型与传输无关,它可以通过提供自己的流控制在原始数据报之上实现,也可以在包含流控制的传输协议上实现,如 VMTP[Cheriton 86]。事实上,Gifford 和 Glasser 在 TCP 之上实现了它。
通道模型可以使用最多一次或恰好一次的语义来实现。然而,人们相信,由于其简单性,信道模型的大多数实现最多只具有一次语义,这与许多其他 RPC 系统类似,例如 Sun Batching RPC。
信道模型的性能可以通过将发送给同一汇聚节点的管道调用缓冲并组合成单个消息来优化,以减少消息处理开销,从而提高吞吐量,这类似于 Sun Batching RPC。其他优化包括将管道调用与过程调用相结合以清除缓冲管道调用,将管道返回组合到单个消息以减少消息处理和系统调用开销,在进程池中预分配进程以消除分叉开销,以及分解包和组以节省空间。在信道模型中,没有尝试区分机器间调用和机器内调用,因此机器内调用没有得到优化。
3.2 异步 RPC 的返回值
尽管所讨论的 RPC 系统提供了某种形式的异步,但它们都不包括延迟接收返回结果的机制。这一缺点限制了分布式应用程序的设计,使其只能从客户端到服务器进行严格的单向交换。在这些系统中,应用程序程序员有三种选择:
1)使用同步 RPC 调用编程应用程序并牺牲并发性; 2)以不需要服务器回复的方式构造应用程序; 3)直接在传输层上编程。鉴于这些缺点,可以延迟接收回复的异步 RPC 系统,如 Stream(Promises)、Future 和 ASTRA 已经被开发出来。
3.2.1 流(Promises)
MIT Mercury 系统中的 Stream 是第一个以干净统一的方式将同步和异步调用与返回值结合起来的 RPC 系统[Liskov et al 88]。Stream 提供三种调用:普通同步 RPC 调用、Stream 调用和 send 调用。流调用是一种带有应答消息的异步 RI~调用,另一方面 Send 与 Sun 批处理 RPC 和远程管道调用类似,因为客户端对回复不感兴趣,除了上述三个调用之外,stream 还提供了一个“flush”原语,可用于清除缓冲的调用或回复消息,以及一个“synch”原语,该原语将阻止调用方,直到所有先前调用的处理完成。
基于流的传输协议(如 TCP)用于可靠地传输和排序流调用和应答消息。它简化了流的实现,并且最多提供一次调用语义。然而,流仅依赖于特定可靠的基于流的传输,这一事实使其更适合于批量数据传输,而不是低延迟调用。此外,对于请求-响应协议更合适的大多数事务性应用程序,使用 TCP 会导致更高的开销。 与 Sun Batching RPC 和远程管道一样,它的设计主要是为了实现高吞吐量,在方便的时候对调用消息进行缓冲和刷新,这是为了减少系统调用开销,虽然通过显式清除调用也可以实现低延迟,但这有点不方便。同样,没有对机器内调用进行优化。
数据流已被整合到 Argus[Liskov 88]中,成为名为 Promises[Liskov and Shrira 88]的新数据类型,无论何时执行流调用,都会向调用者返回一个 Promise。它就像一个邮箱,保存着服务器计算的结果,当它准备好时,客户端可以声明它。声明操作的结果反映流调用的结果,如果调用成功,声明将返回结果的类型;如果调用失败,声明将返回异常的名称和类型。一个承诺可以在任何方便的顺序中多次提出,每次都会返回相同的结果。尽管只在最多一次提供调用语义,但 Promises 能够实现一次调用语义,因为 Argus 计算作为原子事务运行。
承诺的声明、流调用和接收操作如下所示:
这里 x 是对象类型的 Promise,type 是返回结果 y 的数据类型,当调用因异常而终止时,控件只转到 except 部分。
3.2.2 Future
Future[Walker et al. 90]是 CRONUS 系统中提供的异步 RPC[Schantz et al. 86]。Future 是每次客户端调用后返回的对象,它是一个欠条,可用于在稍后阶段声明调用的结果。Future 由存根程序创建和声明,存根程序根据操作规范自动生成。
对于每个被调用的远程操作,存根生成器都会生成一对存根过程:FlnvokeXXX()和 FCIaimXXX()。FlnvokeXXX()用于调用远程操作并返回未来,在 C 语言语法中,FlnvokeXXX()的调用格式如下所示:
在本例中,XXX 是服务器导出的远程操作名称,object 指定要操作的项,statement 指定输入数据,InvokeControl 用于为调用设置各种处理选项,例如服务器所在的主机名和回复到达的绝对时间限制。如果超过绝对时间限制,系统将发出错误信号。另一方面,FCIaimXXX()用于在以后声称未来。FCIaimXXX()的调用格式如下:
这里 XXX 是服务器导出的操作名,future 是 FlnvokeXXX()返回的唯一标识符,output 是远程过程 XXX 返回的输出参数。
此外,还提供了其他三个原语来操纵未来:Discard()、IsReady()和 allowMultipleClaimes()。Discard()通知系统未来不再重要,因此应该销毁;IsReady()测试未来是否已准备好收集;AllowMultipleClaimes()允许在将来声明多个回复。这是将来提供的唯一原语,用于支持具有多个应答的异步调用。 Future 还提供了一个名为 FutureSet 的抽象。这允许将多个 Future 组合到一个集合中,FutureSet 简化了 Future 的管理,消除了索赔操作的严格顺序。例如,原语 FuturesetExtractReady()提取集合中已准备好的任何一个未来。随后可以使用 FCIaimXXX()声明检索到的特定未来,因此 FuturesetExtractReady()类似于 BSD 套接字[Sechrest 86]中的 select()原语。
此外,Future 通过一个名为漏斗的抽象来支持异步调用的流控制。漏斗基本上指定了任何时候允许的未完成 Future 的最大数量,当客户端调用 FlnvokeXXX()时,调用将立即返回到其调用方。但是,只有在未完成 Future 的数量未超过最大值时,才会调用远程操作;否则,调用消息将被保留,直到未完成的 Future 被认领或放弃。因此通过漏斗可以防止服务器过载,但在当前的实现中,使用漏斗的流控制是在应用程序级别处理的,而不是在系统级别。
Future 是在 TCP 和 UDP 上实现的。TCP 是未来支持的主要传输协议。使用 TCP,可以保证调用和回复消息的传递。另一方面,future 不在 UDP 之上提供任何端到端机制,因此基于 UDP 的调用不可靠。虽然 TCP 是一种顺序传输协议,但 Future 不保证调用消息的传递顺序,调用消息可以在传输前的缓冲过程中重新排序[Walker 90],这是一个严重的缺点,因为服务器中的执行顺序可能与客户端调用的顺序不同。
与大多数异步 RPC 系统不同,Future 主要针对低延迟设计的,每个请求都会立即发送调用消息,返回的结果可以按任何顺序声明,在当前的实现中,future 不会忽略机器内部调用的昂贵数据转换和网络通信[Walker 90]。
3.2.3 ASTRA
ASTRA[Ananda et al. 91]是在新加坡国立大学(NUS)信息系统与计算机科学系(DISCS)的分布式计算环境 SHILPA 的框架内构建的。SHILPA 的主要设计目标是提供一个通用的分布式计算平台,用于在异构环境中的局域网互连上构建分布式应用程序。
ASTRA 调用与 Stream 和 Future 调用类似,因为它能够延迟接收结果。客户端可以使用以下原语在 C 语言中进行 ASTRA 调用:
clnthandler 是 rpc_clntit()返回的句柄,它创建并绑定一个客户端句柄;service 是要调用的服务名称/号码;call_option 参数可用于指定各种选项,例如低延迟或高吞吐量。如果调用成功,每个 rpc_clntasycall()调用都会返回一个单调递增的 rpcxid,如果调用出错,则返回一个-1。每个 rpcxid 在 clnthandler 中都是唯一的,用于在以后的阶段为特定调用声明回复消息。要接收 clnthandler 特定调用的回复,客户端可以使用以下原语:
除非 clnthandler 设置为 NO_DELAY,否则如果此特定调用的回复消息不可用,此函数将被阻止。
除了正常的调用和声明原语外,ASTRA 还提供了一个原语 rpc_clntwait(),允许客户端在指定的时间限制内等待回复,还提供了其他几个原语来处理异常情况,包括 rpc_clntping()、rpc_clntry()和 rpc_clntabort()。rpc_clntping()原语用于确定服务器进程的状态;rpc_clntry()原语用于重新尝试特定的调用,如果之前执行过该操作,则无需重新执行该操作;rpc_clntabort()将中止服务器中挂起或执行的调用。
ASTRA 是独立于传输的,因为它不依赖任何特定的通信协议。机器间调用支持两种类型的传输服务:虚电路和可靠数据报。目前支持的传输协议有:TCP/IP 和 RDTP/IP。RDTP 是一种建立在 UDP 之上的可靠数据报传输协议。无论底层传输协议如何,ASTRA 都能提供调用和回复消息。因此,所有调用都由服务器按照客户机调用的顺序接收和执行。此外,ASTRA 旨在实现最多一次调用语义。
ASTRA 将低延迟和高吞吐量通信集成到一个异步 RPC 模型中。用户可以明确指定调用的主要关注点是低延迟还是高吞吐量,系统将相应地优化调用。它与其他异步 RPC 系统(如 Stream 和 Future)不同,后者设计为只实现其中一个,而不是同时实现两个。
与 Stream 和 Future 不同,ASTRA 提供高度优化(轻量级)的机器内调用。对于机器内调用,ASTRA 将绕过数据转换和网络通信,直接使用本地操作系统提供的最快的本机 IPC 机制,这是 ASTRA 提供的独特功能。然而,ASTRA 并没有融合 FutureSet 和漏斗等概念。ASTRA 中的流量控制由底层传输协议完成。
4. 异步 RPC 测试记录
下表是所讨论的异步 RPC 系统的测试记录。这里定义的调用语义与 Spector 的论文[Spector 82]中的定义非常相似,只是我们将 Only-one-Type-1 表示为至多一次。
5. Conclusion
本文简要分析了为什么异步 RPC 是在异构分布式计算环境中实现更高并行性的最合适范例。还讨论了这种机构的设计准则。最后,对近年来发展起来的一些异步 RPC 系统进行了分析和比较。
这项调查揭示了几个要点。首先,目前的趋势是开发具有返回值的异步 RPC 系统。最新的异步 RPC 开发(如 Stream、Future 和 ASTRA)都属于这一类,这一点很明显。其次,这里调查的大多数系统没有针对机器内调用进行优化。一个可能的原因是,直到最近 Bershad 等人报告了他们的发现[Bershad et al. 89],才知道机器内调用的主导地位。最后,虚电路(TCP)是异步 RPC 系统的一种流行传输机制,因为它方便地提供了异步 RPC 调用的可靠性和顺序。
虽然强调了异步 RPC 的重要性,但我们预计正常的同步 RPC 调用将占主导地位,几乎所有的异步 RPC 系统都有一个同步的对应系统,这证明了我们的观点,我们相信未来的 RPC 实现将集成同步和异步调用,以提供统一、完整和全面的远程操作机制,只有这样,分布式应用程序才能得到普遍和简单的支持。