远程服务调用PRC发展史

本文是《凤凰架构》一书的读书笔记。

远程服务调用PRC发展史

RPC 调用简介

RPC(远程服务调用)是指位于互不重合的内存地址空间中(可以是一台机器也可以是不同网络分区的不同机器)的两个程序,在语言层面上,以同步的方式使用带宽有限的信道来传输程序控制信息。

通过网络进行分布式运算的八宗罪(8 Fallaciesof Distributed Computing)

远程服务调用PRC发展史

RPC协议要解决的三个问题

1. 如何表示数据(如何进行序列化)

每种RPC协议都应该要有对应的序列化协议。

  • CORBA的通用数据表示(Common Data Representation,CDR)
  • Java RMI的Java对象序列化流协议(Java Object Serialization StreamProtocol)
  • gRPC的Protocol Buffers
  • Web Service的XML序列化
  • 众多轻量级RPC支持的JSON序列化

2. 如何传递数据(使用什么协议来传递数据)

如何传递数据,准确地说,是指如何通过网络,在两个服务的Endpoint之间相互操作、交换数据。

两个服务交互数据不是只扔个序列化数据流来表示参数和结果就行许多在此之外的信息,譬如异常、超时、安全、认证、授权、事务等,都可能产生双方需要交换信息的需求。在计算机科学中,专门有一个名词“Wire Protocol”来表示这种两个Endpoint之间交换这类数据的行为,常见的Wire Protocol如下。

  • Java RMI的Java远程消息交换协议(Java Remote Message Protocol,JRMP,也支持RMI-IIOP)
  • CORBA的互联网ORB间协议(Internet Inter ORB Protocol,IIOP,是GIOP协议在IP协议上的实现版本)
  • Web Service的简单对象访问协议(Simple Object Access Protocol,SOAP)
  • 如果要求足够简单,双方都是HTTP Endpoint,直接使用HTTP协议也是可以的(如JSON-RPC)

3. 如何表示方法

确定表示方法在本地方法调用中并不是太大的问题,编译器或者解释器会根据语言规范,将调用的方法签名转换为进程空间中子过程入口位置的指针。不过一旦要考虑不同语言,事情又立刻麻烦起来,每种语言的方法签名都可能有差别,所以“如何表示同一个方法”“如何找到对应的方法”还是需要一个统一的跨语言的标准才行。

这个标准可以非常简单,譬如直接给程序的每个方法都规定一个唯一的、在任何机器上都绝不重复的编号,调用时压根不管它是什么方法、签名是如何定义的,直接传这个编号就能找到对应的方法。

类似地,用于表示方法的协议还有:

  • CORBA的OMG接口定义语言(OMG Interface Definition Language,OMGIDL)
  • Web Service的Web服务描述语言(Web Service Description Language,WSDL)
  • JSON-RPC的JSON Web服务协议(JSON Web Service Protocol,JSON-WSP)

没有一个简单、通用、高性能的协议能同时解决上面的问题

面向透明的、简单的RPC协议,如DCE/RPC、DCOM、Java RMI,要么依赖于操作系统,要么依赖于特定语言,总有一些先天约束;

那些面向通用的、普适的RPC协议,如CORBA,就无法逃过使用复杂性的困扰,CORBA烦琐的OMGIDL、ORB都是很好的佐证;

而那些意图通过技术手段来屏蔽复杂性的RPC协议,如Web Service,又不免受到性能问题的束缚。

简单、普适、高性能这三点,似乎真的很难同时满足。

各有侧重点的RPC

由于一直没有一个同时满足以上三点的“完美RPC协议”出现,所以远程服务器调用这个小小的领域,逐渐进入群雄混战、百家争鸣的战国时代,距离“统一”越来越远,并一直延续至今。现在,已经相继出现过RMI(Sun/Oracle)、Thrift(Facebook/Apache)、Dubbo(阿里巴巴/Apache)、gRPC(Google)、Motan1/2(新浪)、Finagle(Twitter)、brpc(百度/Apache)、.NETRemoting(微软)、Arvo(Hadoop)、JSON-RPC 2.0(公开规范,JSON-RPC工作组)等难以穷举的协议和框架。这些RPC功能、特点不尽相同,有的是某种语言私有,有的支持跨越多种语言,有的运行在应用层HTTP协议之上,有的直接运行于传输层TCP/UDP协议之上,但并不存在哪一款是“最完美的RPC”。今时今日,任何一款具有生命力的RPC框架,都不再去追求大而全的“完美”,而是以某个具有针对性的特点作为主要的发展方向。

  • 朝着性能发展,代表为gRPC和Thrift。决定RPC性能的主要因素有两个:序列化效率和信息密度。序列化效率很好理解,序列化输出结果的容量越小,速度越快,效率自然越高;信息密度则取决于协议中有效负载(Payload)所占总传输数据的比例大小,使用传输协议的层次越高,信息密度就越低,SOAP使用XML拙劣的性能表现就是前车之鉴。gRPC和Thrift都有自己优秀的专有序列化器,而传输协议方面,gRPC是基于HTTP/2的,支持多路复用和Header压缩,Thrift则直接基于传输层的TCP协议来实现,省去了应用层协议的额外开销。
  • 朝着简化发展,代表为JSON-RPC,说要选功能最强、速度最快的RPC可能会很有争议,但选功能弱的、速度慢的,JSON-RPC肯定会是候选人之一。牺牲了功能和效率,换来的是协议的简单轻便,接口与格式都更为通用,尤其适合用于Web浏览器这类一般不会有额外协议支持、额外客户端支持的应用场合。

到了最近几年,RPC框架有明显向更高层次(不仅仅负责调用远程服务,还管理远程服务)与插件化方向发展的趋势,不再追求独立地解决RPC的全部三个问题(表示数据、传递数据、表示方法),而是将一部分功能设计成扩展点,让用户自己选择。框架聚焦于提供核心的、更高层次的能力,譬如提供负载均衡、服务注册、可观察性等方面的支持。这一类框架的代表有Facebook的Thrift与阿里的Dubbo

Dubbo默认有自己的传输协议(Dubbo协议),同时也支持其他协议;默认采用Hessian 2作为序列化器,如果你有JSON的需求,可以替换为Fastjson,如果你对性能有更高的追求,可以替换为Kryo、FST、Protocol Buffers等效率更好的序列化器,如果你不想依赖其他组件库,也可以直接使用JDK自带的序列化器。这种设计在一定程度上缓和了RPC框架必须取舍、难以完美的缺憾。

上一篇:有趣的开源项目集结完毕,HelloGitHub 月刊第 63 期发布啦!


下一篇:ORA-28040: No matching authentication protocol