RPC初探

2023-02-28 14:02:38 浏览数 (2)

顺带复习当时使用过的soap协议和wsdl文件,当时使用时还是囫囵吞枣不得其解,今天有幸在周志明老师的书里建立了知识体系,从零散的知识里又拼了一块进去。

RPC 框架要解决的三个基本问题

所有流行过的 RPC 协议,都不外乎通过各种手段来解决三个基本问题:

如何表示数据?

如何传递数据?

如何表示方法?

如何表示数据

这里的数据包括了传递给方法的参数,以及方法的返回值。无论是将参数传递给另外一个进程,还是从另外一个进程中取回执行结果,都会涉及应该如何表示的问题。

针对进程内的方法调用,我们使用程序语言内置的和程序员自定义的数据类型,就很容易解决数据表示的问题了;

而远程方法调用,则可能面临交互双方分属不同程序语言的情况,同语言也会有贷款操作系统硬件差异。行之有效的做法,序列化与反序列化。每种 RPC 协议都应该有对应的序列化协议,比如:

Java RMI 的Java Object Serialization Stream

ProtocolgRPC 的Protocol Buffers

Web Service 的XML Serialization

众多轻量级 RPC 支持的JSON Serialization

如何传递数据

准确地说,如何传递数据是指如何通过网络,在两个服务 Endpoint 之间相互操作、交换数据。这里“传递数据”通常指的是应用层协议,实际传输一般是基于标准的 TCP、UDP 等传输层协议来完成的。两个服务交互不是只扔个序列化数据流来表示参数和结果就行了,诸如异常、超时、安全、认证、授权、事务等信息,都可能存在双方交换信息的需求。

在计算机科学中,专门有一个“Wire Protocol”,用来表示两个 Endpoint 之间交换这类数据的行为。常见的 Wire Protocol 有以下几种:

Java RMI 的Java Remote Message Protocol(JRMP,也支持RMI-IIOP)

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

Web Service 的Simple Object Access Protocol(SOAP)

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

如何表示方法

“如何表示方法”,这在本地方法调用中其实也不成问题,因为编译器或者解释器会根据语言规范,把调用的方法转换为进程地址空间中方法入口位置的指针。

不过一旦考虑到不同语言,这件事儿又麻烦起来了,因为每门语言的方法签名都可能有所差别,所以,针对“如何表示一个方法”和“如何找到这些方法”这两个问题,我们还是得有个统一的标准。

这个标准做起来其实可以很简单:只要给程序中的每个方法,都规定一个通用的又绝对不会重复的编号;在调用的时候,直接传这个编号就可以找到对应的方法。这个唯一的“绝不重复”的编码方案UUID,后来意外地流行了起来,已经被广泛应用到了程序开发的方方面面。

这类用于表示方法的协议还有:

Android 的Android Interface Definition Language(AIDL)

CORBA 的OMG Interface Definition Language(OMG IDL)

Web Service 的Web Service Description Language(WSDL)

JSON-RPC 的JSON Web Service Protocol(JSON-WSP)

你看,如何表示数据、如何传递数据、如何表示方法这三个 RPC 中的基本问题,都可以在本地方法调用中找到对应的操作。RPC 的思想始于本地方法调用,尽管它早就不再追求要跟本地方法调用的实现完全一样了(因为像本地方法一样调用的rpc有”八宗罪“),但 RPC 的发展仍然带有本地方法调用的深刻烙印。因此,我们在理解 PRC 的本质时,比较轻松的方式是,以它和本地调用的联系来对比着理解。好,理解了RPC 要解决的三个基本问题以后,我们接着来看一下,现代的 RPC 框架都为我们提供了哪些可选的解决方案,以及为什么今天会有这么多的 RPC 框架在并行发展。

Web Service一统RPC

1998 年,XML 1.0 发布,并成为了万维网联盟(World Wide Web Consortium,W3C)的推荐标准。1999 年末,以 XML 为基础的 SOAP 1.0(Simple Object Access Protocol)规范的发布,代表着一种被称为“Web Service”的全新 RPC 协议的诞生。

Web Service 是由微软和 DevelopMentor 公司共同起草的远程服务协议,随后被提交给 W3C,并通过投票成为了国际标准。所以,Web Service 也被称为是 W3C Web Service。

Web Service 采用了 XML 作为远程过程调用的序列化、接口描述、服务发现等所有编码的载体,当时 XML 是计算机工业最新的银弹,只要是定义为 XML 的东西,几乎就都被认为是好的,风头一时无两,连微软自己都主动宣布放弃 DCOM,迅速转投 Web Service 的怀抱。交给 W3C 管理后,Web Service 再没有天生属于哪家公司的烙印,商业运作非常成功,很受市场欢迎,大量的厂商都想分一杯羹。但从技术角度来看,它设计得也并不优秀,甚至同样可以说是有显著缺陷。对于开发者而言,Web Service 的一大缺点,就是过于严格的数据和接口定义所带来的性能问题。

虽然 Web Service 使用的 XML 作为一门描述性语言,本身的信息密度就很低(都不用与二进制协议比,与今天的 JSON 或 YAML 比一下就知道了)。

同时,Web Service 是一个跨语言的 RPC 协议,这使得一个简单的字段,为了在不同语言中不会产生歧义,要以 XML 描述去清楚的话,往往比原本存储这个字段值的空间多出十几倍、几十倍乃至上百倍。详情可以看我写过的样例,这是一个提供最新的国内手机号码段归属地数据的wsdl定义,当时使用是2020.7月,就觉得这种定义和使用方式异常繁琐

代码语言:javascript复制
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://WebXml.com.cn/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://WebXml.com.cn/">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"><a href="http://www.webxml.com.cn/" target="_blank">WebXml.com.cn</a> <strong>国内手机号码归属地查询WEB服务</strong>,提供最新的国内手机号码段归属地数据,每月更新。<br />使用本站 WEB 服务请注明或链接本站:<a href="http://www.webxml.com.cn/" target="_blank">http://www.webxml.com.cn/</a> 感谢大家的支持!<br />&nbsp;</wsdl:documentation>
<wsdl:types>
<s:schema elementFormDefault="qualified" targetNamespace="http://WebXml.com.cn/">
<s:element name="getMobileCodeInfo">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="mobileCode" type="s:string"/>
<s:element minOccurs="0" maxOccurs="1" name="userID" type="s:string"/>
</s:sequence>
</s:complexType>
</s:element>
<s:element name="getMobileCodeInfoResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="getMobileCodeInfoResult" type="s:string"/>
</s:sequence>
</s:complexType>
</s:element>
<s:element name="getDatabaseInfo">
<s:complexType/>
</s:element>
<s:element name="getDatabaseInfoResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="getDatabaseInfoResult" type="tns:ArrayOfString"/>
</s:sequence>
</s:complexType>
</s:element>
<s:complexType name="ArrayOfString">
<s:sequence>
<s:element minOccurs="0" maxOccurs="unbounded" name="string" nillable="true" type="s:string"/>
</s:sequence>
</s:complexType>
<s:element name="string" nillable="true" type="s:string"/>
<s:element name="ArrayOfString" nillable="true" type="tns:ArrayOfString"/>
</s:schema>
</wsdl:types>
<wsdl:message name="getMobileCodeInfoSoapIn">
<wsdl:part name="parameters" element="tns:getMobileCodeInfo"/>
</wsdl:message>
<wsdl:message name="getMobileCodeInfoSoapOut">
<wsdl:part name="parameters" element="tns:getMobileCodeInfoResponse"/>
</wsdl:message>
<wsdl:message name="getDatabaseInfoSoapIn">
<wsdl:part name="parameters" element="tns:getDatabaseInfo"/>
</wsdl:message>
<wsdl:message name="getDatabaseInfoSoapOut">
<wsdl:part name="parameters" element="tns:getDatabaseInfoResponse"/>
</wsdl:message>
<wsdl:message name="getMobileCodeInfoHttpGetIn">
<wsdl:part name="mobileCode" type="s:string"/>
<wsdl:part name="userID" type="s:string"/>
</wsdl:message>
<wsdl:message name="getMobileCodeInfoHttpGetOut">
<wsdl:part name="Body" element="tns:string"/>
</wsdl:message>
<wsdl:message name="getDatabaseInfoHttpGetIn"/>
<wsdl:message name="getDatabaseInfoHttpGetOut">
<wsdl:part name="Body" element="tns:ArrayOfString"/>
</wsdl:message>
<wsdl:message name="getMobileCodeInfoHttpPostIn">
<wsdl:part name="mobileCode" type="s:string"/>
<wsdl:part name="userID" type="s:string"/>
</wsdl:message>
<wsdl:message name="getMobileCodeInfoHttpPostOut">
<wsdl:part name="Body" element="tns:string"/>
</wsdl:message>
<wsdl:message name="getDatabaseInfoHttpPostIn"/>
<wsdl:message name="getDatabaseInfoHttpPostOut">
<wsdl:part name="Body" element="tns:ArrayOfString"/>
</wsdl:message>
<wsdl:portType name="MobileCodeWSSoap">
<wsdl:operation name="getMobileCodeInfo">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"><br /><h3>获得国内手机号码归属地省份、地区和手机卡类型信息</h3><p>输入参数:mobileCode = 字符串(手机号码,最少前7位数字),userID = 字符串(商业用户ID) 免费用户为空字符串;返回数据:字符串(手机号码:省份 城市 手机卡类型)。</p><br /></wsdl:documentation>
<wsdl:input message="tns:getMobileCodeInfoSoapIn"/>
<wsdl:output message="tns:getMobileCodeInfoSoapOut"/>
</wsdl:operation>
<wsdl:operation name="getDatabaseInfo">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"><br /><h3>获得国内手机号码归属地数据库信息</h3><p>输入参数:无;返回数据:一维字符串数组(省份 城市 记录数量)。</p><br /></wsdl:documentation>
<wsdl:input message="tns:getDatabaseInfoSoapIn"/>
<wsdl:output message="tns:getDatabaseInfoSoapOut"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:portType name="MobileCodeWSHttpGet">
<wsdl:operation name="getMobileCodeInfo">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"><br /><h3>获得国内手机号码归属地省份、地区和手机卡类型信息</h3><p>输入参数:mobileCode = 字符串(手机号码,最少前7位数字),userID = 字符串(商业用户ID) 免费用户为空字符串;返回数据:字符串(手机号码:省份 城市 手机卡类型)。</p><br /></wsdl:documentation>
<wsdl:input message="tns:getMobileCodeInfoHttpGetIn"/>
<wsdl:output message="tns:getMobileCodeInfoHttpGetOut"/>
</wsdl:operation>
<wsdl:operation name="getDatabaseInfo">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"><br /><h3>获得国内手机号码归属地数据库信息</h3><p>输入参数:无;返回数据:一维字符串数组(省份 城市 记录数量)。</p><br /></wsdl:documentation>
<wsdl:input message="tns:getDatabaseInfoHttpGetIn"/>
<wsdl:output message="tns:getDatabaseInfoHttpGetOut"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:portType name="MobileCodeWSHttpPost">
<wsdl:operation name="getMobileCodeInfo">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"><br /><h3>获得国内手机号码归属地省份、地区和手机卡类型信息</h3><p>输入参数:mobileCode = 字符串(手机号码,最少前7位数字),userID = 字符串(商业用户ID) 免费用户为空字符串;返回数据:字符串(手机号码:省份 城市 手机卡类型)。</p><br /></wsdl:documentation>
<wsdl:input message="tns:getMobileCodeInfoHttpPostIn"/>
<wsdl:output message="tns:getMobileCodeInfoHttpPostOut"/>
</wsdl:operation>
<wsdl:operation name="getDatabaseInfo">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"><br /><h3>获得国内手机号码归属地数据库信息</h3><p>输入参数:无;返回数据:一维字符串数组(省份 城市 记录数量)。</p><br /></wsdl:documentation>
<wsdl:input message="tns:getDatabaseInfoHttpPostIn"/>
<wsdl:output message="tns:getDatabaseInfoHttpPostOut"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="MobileCodeWSSoap" type="tns:MobileCodeWSSoap">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getMobileCodeInfo">
<soap:operation soapAction="http://WebXml.com.cn/getMobileCodeInfo" style="document"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getDatabaseInfo">
<soap:operation soapAction="http://WebXml.com.cn/getDatabaseInfo" style="document"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:binding name="MobileCodeWSSoap12" type="tns:MobileCodeWSSoap">
<soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getMobileCodeInfo">
<soap12:operation soapAction="http://WebXml.com.cn/getMobileCodeInfo" style="document"/>
<wsdl:input>
<soap12:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap12:body use="literal"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getDatabaseInfo">
<soap12:operation soapAction="http://WebXml.com.cn/getDatabaseInfo" style="document"/>
<wsdl:input>
<soap12:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap12:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:binding name="MobileCodeWSHttpGet" type="tns:MobileCodeWSHttpGet">
<http:binding verb="GET"/>
<wsdl:operation name="getMobileCodeInfo">
<http:operation location="/getMobileCodeInfo"/>
<wsdl:input>
<http:urlEncoded/>
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getDatabaseInfo">
<http:operation location="/getDatabaseInfo"/>
<wsdl:input>
<http:urlEncoded/>
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:binding name="MobileCodeWSHttpPost" type="tns:MobileCodeWSHttpPost">
<http:binding verb="POST"/>
<wsdl:operation name="getMobileCodeInfo">
<http:operation location="/getMobileCodeInfo"/>
<wsdl:input>
<mime:content type="application/x-www-form-urlencoded"/>
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getDatabaseInfo">
<http:operation location="/getDatabaseInfo"/>
<wsdl:input>
<mime:content type="application/x-www-form-urlencoded"/>
</wsdl:input>
<wsdl:output>
<mime:mimeXml part="Body"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="MobileCodeWS">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"><a href="http://www.webxml.com.cn/" target="_blank">WebXml.com.cn</a> <strong>国内手机号码归属地查询WEB服务</strong>,提供最新的国内手机号码段归属地数据,每月更新。<br />使用本站 WEB 服务请注明或链接本站:<a href="http://www.webxml.com.cn/" target="_blank">http://www.webxml.com.cn/</a> 感谢大家的支持!<br />&nbsp;</wsdl:documentation>
<wsdl:port name="MobileCodeWSSoap" binding="tns:MobileCodeWSSoap">
<soap:address location="http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx"/>
</wsdl:port>
<wsdl:port name="MobileCodeWSSoap12" binding="tns:MobileCodeWSSoap12">
<soap12:address location="http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx"/>
</wsdl:port>
<wsdl:port name="MobileCodeWSHttpGet" binding="tns:MobileCodeWSHttpGet">
<http:address location="http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx"/>
</wsdl:port>
<wsdl:port name="MobileCodeWSHttpPost" binding="tns:MobileCodeWSHttpPost">
<http:address location="http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

这个特点就导致了,要想使用 Web Service,就必须要有专门的客户端去调用和解析 SOAP 内容,也需要专门的服务去部署(如 Java 中的 Apache Axis/CXF);更关键的是,这导致了每一次数据交互都包含大量的冗余信息,性能非常差。如果只是需要客户端、传输性能差也就算了,又不是不能用。既然选择了 XML 来获得自描述能力(名称:值的形式,不像json只描述值),也就代表着没打算把性能放到第一位。

但是,Web Service 还有另外一点原罪:贪婪。“贪婪”是指,它希望在一套协议上,一揽子解决分布式计算中可能遇到的所有问题。这导致 Web Service 生出了一整个家族的协议出来。Web Service 协议家族中,除它本身包括了的 SOAP、WSDL、UDDI 协议之外,还有一堆以WS-*命名的子功能协议,来解决事务、一致性、事件、通知、业务描述、安全、防重放等问题。这些几乎数不清个数的家族协议,对开发者来说学习负担极其沉重。结果就是,得罪惨了开发者,谁爱用谁用去。当程序员们对 Web Service 的热情迅速燃起,又逐渐冷却之后,也不禁开始反思:那些面向透明的、简单的 RPC 协议,如 DCE/RPC、DCOM、Java RMI,要么依赖于操作系统,要么依赖于特定语言,总有一些先天约束;那些面向通用的、普适的 RPC 协议,如 CORBA,就无法逃过使用复杂性的困扰;而那些意图通过技术手段来屏蔽复杂性的 RPC 协议,如 Web Service,又不免受到性能问题的束缚。简单、普适和高性能,似乎真的难以同时满足。分裂的 RPC由于一直没有一个能同时满足以上简单、普适和高性能的“完美 RPC 协议”,因此远程服务器调用这个小小的领域就逐渐进入了群雄混战、百家争鸣的“战国时代”,距离“统一”越来越远,并一直延续至今。

群雄争霸

我们看看相继出现过的 RPC 协议 / 框架,就能明白了:RMI(Sun/Oracle)、Thrift(Facebook/Apache)、Dubbo(阿里巴巴 /Apache)、gRPC(Google)、Motan2(新浪)、Finagle(Twitter)、brpc(百度)、.NET Remoting(微软)、Arvo(Hadoop)、JSON-RPC 2.0(公开规范,JSON-RPC 工作组)……这些 RPC 的功能、特点都不太一样,有的是某种语言私有,有的能支持跨越多门语言,有的运行在 HTTP 协议之上,有的能直接运行于 TCP/UDP 之上,但没有哪一款是“最完美的 RPC”。据此,我们也可以发现一个规律,任何一款具有生命力的 RPC 框架,都不再去追求大而全的“完美”,而是会找到一个独特的点作为主要的发展方向。

面向对象

我们看几个典型的发展方向:朝着面向对象发展。这条线的缘由在于,在分布式系统中,开发者们不再满足于 RPC 带来的面向过程的编码方式,而是希望能够进行跨进程的面向对象编程。因此,这条线还有一个别名叫作分布式对象(Distributed Object),它的代表有 RMI、.NET Remoting。当然了,之前的 CORBA 和 DCOM 也可以归入这一类。

唯快不破

朝着性能发展,代表为 gRPC 和 Thrift。决定 RPC 性能主要就两个因素:序列化效率和信息密度。序列化效率很好理解,序列化输出结果的容量越小,速度越快,效率自然越高;信息密度则取决于协议中,有效荷载(Payload)所占总传输数据的比例大小,使用传输协议的层次越高,信息密度就越低,SOAP 使用 XML 拙劣的性能表现就是前车之鉴。gRPC 和 Thrift 都有自己优秀的专有序列化器,而在传输协议方面,gRPC 是基于 HTTP/2 的,支持多路复用和 Header 压缩,Thrift 则直接基于传输层的 TCP 协议来实现,省去了额外的应用层协议的开销。

化繁为简

朝着简化发展,代表为 JSON-RPC。要是说选出功能最强、速度最快的 RPC 可能会有争议,但要选出哪个功能弱的、速度慢的,JSON-RPC 肯定会是候选人之一。它牺牲了功能和效率,换来的是协议的简单。也就是说,JSON-RPC 的接口与格式的通用性很好,尤其适合用在 Web 浏览器这类一般不会有额外协议、客户端支持的应用场合。……经历了 RPC 框架的“战国时代”,开发者们终于认可了,不同的 RPC 框架所提供的不同特性或多或少是互相矛盾的,很难有某一种框架说“我全部都要”。要把面向对象那套全搬过来,就注定不会太简单(比如建 Stub、Skeleton 就很烦了,即使由 IDL 生成也很麻烦);功能多起来,协议就要弄得复杂,效率一般就会受影响;要简单易用,那很多事情就必须遵循约定而不是配置才行;要重视效率,那就需要采用二进制的序列化器和较底层的传输协议,支持的语言范围容易受限。也正是因为每一种 RPC 框架都有不完美的地方,才会有新的 RPC 轮子不断出现。

路在何方

而到了最近几年,RPC 框架有明显朝着更高层次(不仅仅负责调用远程服务,还管理远程服务)与插件化方向发展的趋势,不再选择自己去解决表示数据、传递数据和表示方法这三个问题,而是将全部或者一部分问题设计为扩展点,实现核心能力的可配置,再辅以外围功能,如负载均衡、服务注册、可观察性等方面的支持。

后起之秀

这一类框架的代表,有 Facebook 的 Thrift 和阿里的 Dubbo(现在两者都是 Apache 的)。尤其是断更多年后重启的 Dubbo 表现得更为明显,它默认有自己的传输协议(Dubbo 协议),同时也支持其他协议,它默认采用 Hessian 2 作为序列化器,如果你有 JSON 的需求,可以替换为 Fastjson;如果你对性能有更高的需求,可以替换为Kryo、FST、Protocol Buffers 等;如果你不想依赖其他包,直接使用 JDK 自带的序列化器也可以。

这种设计,就在一定程度上缓解了 RPC 框架必须取舍,难以完美的缺憾。小结今天,我们一起学习了 RPC 协议在工业界的发展,包括它要解决的三个基本问题,以及层出不穷的 RPC 协议 / 框架。表示数据、传递数据和表示方法,是 RPC 必须解决的三大基本问题。要解决这些问题,可以有很多方案,这也是 RPC 协议 / 框架出现群雄混战局面的一个原因。

出现这种分裂局面的另一个原因,是简单的框架很难能达到功能强大的要求。

功能强大的框架往往要在传输中加入额外的负载和控制措施,导致传输性能降低,而如果既想要高性能,又想要强功能,这就必然要依赖大量的技巧去实现,进而也就导致了框架会变得过于复杂,这就决定了不可能有一个“完美”的框架同时满足简单、普适和高性能这三个要求。

认识到这一点后,一个 RPC 框架要想取得成功,就要选择一个发展方向,能够非常好地满足某一方面的需求。因此,我们也就有了朝着面向对象发展、朝着性能发展和朝着简化发展这三条线。以上就是这一讲我要和你分享的 RPC 在工业界的发展成果了。

这也是,你在日后工作中选择 RPC 实现方案的一个参考。最后,我再和你分享一点我的心得。我在讲到 DCOM、CORBA、Web Service 的失败的时候,虽然说我的口吻多少有一些戏谑,但我们得明确一点:这些框架即使没有成功,但作为早期的探索先驱,并没有什么应该被讽刺的地方。而且其后续的发展,都称得上是知耻后勇,反而值得我们的掌声赞赏。比如,说到 CORBA 的消亡,OMG 痛定思痛之后,提出了基于 RTPS 协议栈的“数据分发服务”商业标准(Data Distribution Service,DDS,“商业”就是要付费使用的意思)。这个标准现在主要用在物联网领域,能够做到微秒级延时,还能支持大规模并发通讯。再比如,说到 DCOM 的失败和 Web Service 的衰落,微软在它们的基础上,推出了.NET WCF(Windows Communication Foundation,Windows 通信基础)。.NET WCF 的优势主要有两点:一是,把 REST、TCP、SOAP 等不同形式的调用,自动封装为了完全一致的、如同本地方法调用一般的程序接口;二是,依靠自家的“地表最强 IDE”Visual Studio,把工作量减少到只需要指定一个远程服务地址,就可以获取服务描述、绑定各种特性(如安全传输)、自动生成客户端调用代码,甚至还能选择同步还是异步之类细节的程度。虽然.NET WCF 只支持.NET 平台,而且也是采用 XML 语言描述,但使用体验真的是非常畅快,足够挽回 Web Service 得罪开发者丢掉的全部印象分。

后续思考

回过头来思考一个问题:开发一个分布式系统,是不是就一定要用 RPC 呢?RPC 的三大问题源自对本地方法调用的类比模拟,如果我们把思维从“方法调用”的约束中挣脱,那参数与结果如何表示、方法如何表示、数据如何传递这些问题,都会海阔天空,拥有焕然一新的视角。

0 人点赞