架构解密从分布式到微服务:古老又有生命力的 RPC

古老又有生命力的RPC

RPC (Remote Procedure Call,远程过程调用)是建立在Socket之上的一种多进程间的通信机制。不同于复杂的Socket通信方式,RPC的初心是设计一套远程通信的通用框架,这个框架能够自动处理通信协议、对象序列化、网络传输等复杂细节,并且希望开发者在使用这个框架以后,调用一个远程机器上的接口的代码与以本地方法调用的代码“看起来没什么区别”,从而大大减小分布式系统的开发难度,使得比较容易开发分布式系统。

为了便于理解Socket通信与RPC通信在编程方面的区别,我们举个简单的例子来解释:假设目前在B机器上有一个进程,可以简单地实现四则运算,比如我们输入 1+1让它计算并返回计算结果,那么用Socket开发时,客户端的伪代码大致如下:

client =new Socket (B);
client.write("plus(1,1)");
result=client.read();
client.close(;

而服务端的伪代码大致如下:

socketServer server=new ServerSocket (;
server.listen (;
while(true)
{
     
cmd-server.read();
if(cmd .startwith("plus("))
{
     
··
client.write(result);
}
}

上述代码仅为大量简化后的伪代码,如果要达到生产质量的要求,则还需要考虑如下复杂问题。

  • 网络异常问题:在调用过程中如果发生网络异常,则调用失败,客户端需要明确知道发生了异常,然后有针对性地进行处理。
  • 复杂数据传输过程中的编码和解码问题:当输入参数或者输出参数很复杂时,参数编码及解码过程中的复杂性经常会让思维不够严密的程序员头脑“短路”。
  • 客户端的连接复用问题,如果每次调用都建立一个TCP连接,用完关闭,那么调用性会很低,因为将大量时间都用在TCP建立连接的过程中了,因此客户端需要一种连接保持及连接复用的机制,还涉及服务端与客户端连接心跳检测及超时机制等相关的复杂问题。
  • 服务端需要有多线程机制来应对客户端的并发请求,以提升性能。

所以你会发现,即使我们有了Socket,有了好的NIO框架,也基本上没有多少人能开发出一个基于Socket的高质量的远程通信模块,而随便一个分布式系统就有很多远程通信的功能点,如此一来,开发一个分布式系统仍然是一件很难的事。

于是,分布式系统中最重要的一个开发框架诞生了,这就是大名鼎鼎的RPC。RPC最初由Sun公司提出,即 Sun RPC,后来也成为IETF国际标准,至今仍然重要的NFS协议就是最早的基于RPC的一个重要案例。

为了将一个传统的程序改写成RPC程序,我们要在程序里加入另外一些代码,这个过程叫作Stub。我们可以想象一个传统程序,它的一个进程被转移到一个远程机器中,在客户端及服务端分别有一个Stub模块实现了远程过程调用所需要的通信功能,比如参数及调用结果的序列化功能,并通过网络完成远程传输,因为Stub与原来的Server端使用了同样的接口,因此增加这些Stub 代码既不需要更改原来Client端的调用逻辑,也不需要更改Server端的逻辑代码,这个过程如下图所示。

架构解密从分布式到微服务:古老又有生命力的 RPC_第1张图片

整个RPC的调用流程如下。

(1)服务消费方(Client)以本地调用方式调用服务。

(2)Client Stub在接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体。(3)Client Stub找到服务地址,并将消息发送到服务端。

( 4)Server Stub在收到消息后进行解码。

( 5 )Server Stub根据解码结果调用本地服务。(6)本地服务执行并将结果返回给Server Stub。

(7) Server Stub将返回结果打包成消息并发送到消费方。(8)Client Stub接收到消息并进行解码。

(9)服务消费方得到最终结果。

要实现一个完整的RPC 架构,就需要如下专有技术。

  • 高性能网络编程技术。
  • 对象(复杂数据结构)序列化与反序列化技术。
  • 自动代码生成或者动态代理编程技术。

比如Java里经典的RPC实现方案RMI,就用到了Java 默认的序列化机制和动态代理编程技术。不过,Java里的RMI及其他语言里特定的RPC架构大多存在一个很明显的缺陷,即仅限于本语言的客户端调用,换种语言就无法调用了。而开发需要支持多语言的RPC架构,其难度至少提升了一个数量级。

在人类历史上,支持多语言通信的第一次伟大尝试造就了功败垂成的CORBA技术,1991年CORBA 1.1诞生,直到1994年年底才完成了CORBA2.0规范,该规范希望能够解决不同厂商根据COBRA规范所开发的产品“互联互不通”的严重问题,可惜还是失败了。至于COBRA失败的原因,一位COBRA技术大牛、COBRA技术的推动者,即后来加入“反COBRA阵营”的Michi Henning,在他的The rise and fall of CORBA书里做了如下深刻的总结。

  • 规范巨大而复杂:许多特性都未曾实现,甚至概念性的证明都没有做过;有些技术特性根本不可能实现,即使实现,也无法提供可移植性。
  • CORBA很难学习:平台的学习曲线陡峭,技术复杂,不容易正确使用,这些因素导致开发周期长、易出错。早期的实现常常充满 Bug 并且缺乏有质量的文档,有经验的CORBA程序员稀缺。
  • 编程开发过于复杂:有经验的CORBA开发者发现编写实用的CORBA应用程序相当困难。许多API 都很复杂、不一致,甚至让人感觉神秘,使得开发者必须关注许多细节问题。相比之下,组件模型的简单性,例如同时代的EJB,使得编程简单很多。
  • 费用昂贵:在使用商用CORBA 产品时,开发者一般都需要花费几千美元购买开发者License,此外,部署CORBA产品与部署Oracle数据库一样,还需要客户支付企业License费用,而且这个费用很可能与部署在CORBA平台上的应用数量挂钩,因此对很多潜在的客户来说,CORBA这样的平台太昂贵了。
  • Sun 与Java成为COBRA最大的竞争对手:商业公司转向了Sun的Java与新兴的Web,并且开始构建基于Web浏览器、Java和EJB的电子商务基础设施。
  • XML技术的兴起加速了COBRA 的没落:20世纪90年代后期,XML成为计算机工业新的银弹,几乎被定义为XML的事物都是好的。在放弃了DCOM 之后,微软并没有把电子商务市场留给竞争对手,没有再参与一场不可能打赢的战争,而是使用XML开辟了新的战场。1999年年底,工业界看到了SOAP的发布。SOAP由微软和DevelopMentor 发布,随后提交给W3C作为标准。SOAP使用XML作为RPC新的对象序列化机制,IBM则又继续发扬光大这条路线,推出Web Service等整套方案。

SOAP在严格意义上是属于XML-RPC (XML Remote Procedure Call)技术的一个变种,一个XML-RPC请求消息就是一个HTTP-POST 请求消息,其请求消息主体基于XML格式。客户端发送XML-RPC 请求消息到服务端,调用服务端的远程方法并在服务端运行远程方法。远程方法在执行完毕后返回响应消息给客户端,其响应消息主体同样基于XML格式。远程方法的参数支持数字、字符串、日期等,也支持列表数组和其他复杂结构类型。SOAP也是第一次真正成功地解决了多语言多平台支持的开放性RPC标准。

一个SOAP请求报文实例(查询股票价格)如下:

<?xml version="1.0"?><soap :Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www .w3.org/2001/12/soap-encoding"><soap:Body xmlns:m="http://www.example.org/stock">
<m:GetStockPrice>
<m: StockName>IBM</m:StockName></m:GetStockPrice>
</soap:Body>
</soap :Envelope>

对应的应答报文实例如下:

<?xml version="l.0"2><soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingstyle="http:/ /www .w3.org/2001/12/soap-encoding"><soap:Body xmlns:m="http://www.example.org/stock">
<m :GetStockPriceResponse>
<m: Price>34.5</m:Price></m: GetStockPriceResponse></soap:Body>
</soap:Envelope>

我们看到,SOAP 的报文很复杂而且编码臃肿,由于它是面向机器识别的表达格式,所以程序员很难直接理解它的报文,该缺陷最终导致了SOAP的末路与HTTP REST的通信方式的兴起。HTTP REST采用了让人容易理解的JSON格式来传递请求与应答参数,因而开发更为方便,但HTTP REST已经脱离了RPC的范畴,最明显的几个特征:它无须客户端Stub代码与服务端Stub 代码,调用也不再类似于本地方法调用方式了。

在RPC的路线演化过程中虽然意外地产生了HTTP REST这个慢慢侵占了RPC大部分应用领地的“异类”,并且导致了一度盛行的XML-RPC的“灭绝”,但同时推动正统RPC技术走向一个新的发展阶段,追求更高的性能及增加对多语言多平台的支持,成为越来越多的开源RPC架构的目标。其典型的代表为Thrift、Avro 等新生的开源框架,这些框架在大数据系统、大型分布式系统及移动互联网应用方面被越来越多的公司使用。

之后,最初参与CORBA的技术专家们打造了延续至今的RPC平台——ZeroC lce。现在,ZeroC Ice已经成为一个很强大的微服务架构平台,很适合作为大型分布式系统、电商系统、电信金融等关键业务系统的基础架构。

RPC技术发展至今,虽然是相对古老的传统技术,却有着其独特的优势,特别是拥有高性能传输及支持高并发请求的绝对优势,使得RPC技术在互联网时代又一次被巨头们所重视。

其中一个典型的代表是Facebook开源的跨语言的RPC架构Thrift。Thrift 于2008年被贡献给Apache,目前支持多达25种编程语言。Thrift 与ZeroC Ice属于COBRA一脉相传的“很正统”的RPC实现方案,使用方式也很类似,即先编写服务接口的IDL文件,然后利用框架提供的编译生成器工具自动生成Server端的骨架代码和客户端的调用代码,最后由程序员填充骨架代码。

另一个典型的代表是谷歌于2015年开源的跨语言的RPC框架——gRPC,gRPC采用的默认的编码机制也是谷歌设计的ProtoBuf。gRPC支持在任意环境下使用,支持物联网、手机、浏览器。支持浏览器这一点很关键,它表明gRPC 的定位及与传统RPC的不同。gRPC没有基于传统的自定义TCP Socket传输通道,而是基于现有的HTTP 2.0!这样看来,gRPC的性能肯定比不过ZeroC Ice、Thrift这些传统RPC,但更通用、直接面向浏览器、取得更大的影响力才是谷歌推出 gRPC的初心。目前用Go开发的分布式系统,比较著名的如Kuberntes、Istio等,都是以gRPC作为分布式通信的接口协议的。

最后

给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。

阅读地址:一篇神文就把java多线程,锁,JMM,JUC和高并发设计模式讲明白了

码字不易,如果觉得本篇文章对你有用的话,请给我一键三连!关注作者,后续会有更多的干货分享,请持续关注!

你可能感兴趣的:(编程语言,java,rpc,程序员编程,高并发编程)