C/C++教程

访问远程服务-RPC 与 REST

本文主要是介绍访问远程服务-RPC 与 REST,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

方法调用 所做的传递参数、传回结果都依赖于栈内存。所以Caller 调用者 Callee 被调用者应该同属一个进程,拥有相同的 栈内存,

进程间通信(Inter-Process Communication,IPC)

·管道(Pipe)或者具名管道(Named Pipe)

管道类似于两个进程间的桥梁,可通过管道在进程间传递少量的字符流或字节流。普通管道只用于有亲缘关系的进程(由一个进程启动的另外一个进程)间的通信,具名管道摆脱了普通管道没有名字的限制,除具有管道的所有功能外,它还允许无亲缘关系的进程间的通信。管道典型的应用就是命令行中的“|”操作符,譬如:

ps -ef | grep java

ps与grep都有独立的进程,以上命令就是通过管道操作符“|”将ps命令的标准输出连接到grep命令的标准输入上。

 

·信号(Signal)

信号用于通知目标进程有某种事件发生。除了进程间通信外,进程还可以给进程自身发送信号。信号的典型应用是kill命令,譬如:

kill -9 pid

以上命令即表示由Shell进程向指定PID的进程发送SIGKILL信号。


·信号量(Semaphore)

信号量用于在两个进程之间同步协作手段,它相当于操作系统提供的一个特殊变量,程序可以在上面进行wait()和notify()操作。


·消息队列(Message Queue)

以上三种方式只适合传递少量消息,POSIX标准中定义了可用于进程间数据量较多的通信的消息队列。进程可以向队列添加消息,被赋予读权限的进程还可以从队列消费消息。消息队列克服了信号承载信息量少、管道只能用于无格式字节流以及缓冲区大小受限等缺点,但实时性相对受限。


·共享内存(Shared Memory)

允许多个进程访问同一块公共内存空间,这是效率最高的进程间通信形式。原本每个进程的内存地址空间都是相互隔离的,但操作系统提供了让进程主动创建、映射、分离、控制某一块内存的程序接口。当一块内存被多进程共享时,各个进程往往会与其他通信机制,譬如与信号量结合使用,来达到进程间同步及互斥的协调操作。


·本地套接字接口(IPC Socket)

消息队列与共享内存只适合单机多进程间的通信,套接字接口则是更普适的进程间通信机制,可用于不同机器之间的进程通信。出于效率考虑,当仅限于本机进程间通信时,套接字接口是被优化过的,不会经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等操作,只是简单地将应用层数据从一个进程复制到另一个进程,这种进程间通信方式即本地套接字接口(UNIX Domain Socket),又叫作IPC Socket。

 之所以花费那么多篇幅来介绍IPC的手段,是因为最初计算机科学家们的想法,就是将RPC作为IPC的一种特例来看待。

请特别注意最后一种基于套接字接口的通信方式(IPC Socket),它不仅适用于本地相同机器的不同进程间通信,由于Socket是网络栈的统一接口,它也能支持基于网络的跨机进程间通信。

这样做的好处是,由于Socket是各个操作系统都提供的标准接口,完全有可能把远程方法调用的通信细节隐藏在操作系统底层,从应用层面上来看可以做到远程调用与本地的进程间通信在编码上完全一致。事实上,在原始分布式时代的早期确实是奔着这个目标去做的,但这种透明的调用形式反而给程序员带来通信无成本的假象,因而被滥用,以致于显著降低了分布式系统的性能。

 

几十年所有流行过的RPC协议,都不外乎变着花样使用各种手段来解决以下三个基本问题。
1.如何表示数据

这里的数据包括传递给方法的参数以及方法执行后的返回值。每种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等标准的传输层协议来完成的。两个服务交互不是只扔个序列化数据流来表示参数和结果就行,许多在此之外的信息,譬如异常、超时、安全、认证、授权、事务等,都可能产生双方需要交换信息的需求。如果要求足够简单,双方都是HTTP Endpoint,直接使用HTTP协议也是可以的(如JSON-RPC)

3.如何表示方法

不过一旦要考虑不同语言,事情又立刻麻烦起来,每种语言的方法签名都可能有差别,所以“如何表示同一个方法”“如何找到对应的方法”还是需要一个统一的跨语言的标准才行。

 

CORBA没有把握住统一RPC的大好时机,很快另外一个更有希望的机会降临。1998年,XML 1.0发布,并成为万维网联盟(World Wide Web Consortium,W3C)的推荐标准。Web Service采用XML作为远程过程调用的序列化、接口描述、服务发现等所有编码的载体,当时XML是计算机工业最新的银弹,只要是定义为XML的东西几乎都被认为是好的,风头一时无两,连微软自己都主动宣布放弃DCOM,迅速转投Web Service的怀抱。但从技术角度来看,它设计得并不优秀,甚至同样可以说是有显著缺陷的。对于开发者而言,Web Service的一大缺点是它过于严格的数据和接口定义所带来的性能问题。

可是,XML作为一门描述性语言本身信息密度就相对低下,(都不用与二进制协议比,与今天的JSON或YAML比一下就知道了。Web Service又是跨语言的RPC协议,这使得一个简单的字段,为了在不同语言中不会产生歧义,要以XML严谨描述的话,往往需要比原本存储这个字段值多出十几倍、几十倍乃至上百倍的空间。但Web Service还有另外一个缺点:贪婪。“贪婪”是指它希望在一套协议上一揽子解决分布式计算中可能遇到的所有问题,这促使Web Service生出了整个家族的协议。

那些面向透明的、简单的RPC协议,如DCE/RPC、DCOM、Java RMI,要么依赖于操作系统,要么依赖于特定语言,总有一些先天约束;那些面向通用的、普适的RPC协议,如CORBA,就无法逃过使用复杂性的困扰,CORBA烦琐的OMG IDL、ORB都是很好的佐证;而那些意图通过技术手段来屏蔽复杂性的RPC协议,如Web Service,又不免受到性能问题的束缚。简单、普适、高性能这三点,似乎真的很难同时满足。由于一直没有一个同时满足以上三点的“完美RPC协议”出现,所以远程服务器调用这个小小的领域,逐渐进入群雄混战、百家争鸣的战国时代,距离“统一”越来越远,并一直延续至今。

今时今日,任何一款具有生命力的RPC框架,都不再去追求大而全的“完美”,而是以某个具有针对性的特点作为主要的发展方向,举例分析如下。
·朝着面向对象发展

不满足于RPC将面向过程的编码方式带到分布式,希望在分布式系统中也能够进行跨进程的面向对象编程,代表为RMI、.NET Remoting,之前的CORBA和DCOM也可以归入这类。这种方式有一个别名叫作分布式对象(Distributed Object)。

·朝着性能发展

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

-朝着简化发展

代表为JSON-RPC,说要选功能最强、速度最快的RPC可能会很有争议,但选功能弱的、速度慢的,JSON-RPC肯定会是候选人之一。牺牲了功能和效率,换来的是协议的简单轻便,接口与格式都更为通用,尤其适合用于Web浏览器这类一般不会有额外协议支持、额外客户端支持的应用场合。

开发者们终于认可了不同的RPC框架所提供的特性或多或少是有矛盾的,很难有某一种框架能满足所有需求。若要朝着面向对象发展,就注定不会太简单,如建Stub、Skeleton就很烦了,即使由IDL生成也很麻烦;功能多起来,协议就会更复杂,效率一般也会受影响;要简单易用,那很多事情就必须遵循约定而不是自行配置;要重视效率,那就需要采用二进制的序列化器和较底层的传输协议,支持的语言范围容易受限。也正是每一种RPC框架都有不完美的地方,所以才导致不断有新的RPC轮子出现,也决定了在选择框架时,在获得一些利益的同时,要付出另外一些代价。

到了最近几年,RPC框架有明显向更高层次(不仅仅负责调用远程服务,还管理远程服务)与插件化方向发展的趋势,不再追求独立地解决RPC的全部三个问题(表示数据、传递数据、表示方法),而是将一部分功能设计成扩展点,让用户自己选择。尤其是断更多年后重启的Dubbo表现得更为明显。Dubbo默认有自己的传输协议(Dubbo协议),同时也支持其他协议;默认采用Hessian 2作为序列化器,如果你有JSON的需求,可以替换为Fastjson,如果你对性能有更高的追求,可以替换为Kryo、FST、Protocol Buffers等效率更好的序列化器,如果你不想依赖其他组件库,也可以直接使用JDK自带的序列化器。

 

其实,REST无论是在思想上、在概念上,还是在使用范围上,与RPC都不尽相同,充其量只能算是有一些相似,应用会有一部分重合之处,但本质上并不是同一类型的东西。
REST与RPC在思想上差异的核心是抽象的目标不一样,即面向过程的编程思想与面向资源的编程思想两者之间的区别。

REST虽然有宽阔的用武之地,只要支持HTTP就可以用于任何语言之间的交互,不过通常都会以网络没有成为性能瓶颈为使用前提,在需要追求传输效率的场景里,REST提升传输效率的潜力有限,死磕REST又想要好的网络性能,一般不会有好的效果;对追求简化调用的场景——前面提到的浏览器端就属于这一类的典型,众多RPC里也只有JSON-RPC有机会与REST竞争。尽管有着种种不同,REST与RPC还是引发了很频繁的比较与争论,这两种分别面向资源和过程的远程调用方式,就如同当年面向对象与过程的编程思想一样,非得分出高低不可。

 

REST 的关键概念。REST(Representational State Transfer,表征状态转移)

·资源 (Resource)

·表征(Representation)

服务端向浏览器返回的这个HTML就被称为“表征”,你也可以通过其他方式拿到本文的PDF、Markdown、RSS等其他形式的版本,它们同样是一个资源的多种表征。

·状态(State)

当你读完了这篇文章,想看后面是什么内容时,你向服务端发出“给我下一篇文章”的请求。但是“下一篇”是个相对概念,必须依赖“当前你正在阅读的文章是哪一篇”才能正确回应。我们所说的有状态(Stateful)抑或是无状态(Stateless),都是只相对于服务端来说的,服务端要完成“取下一篇”的请求,

要么自己记住用户的状态,如这个用户现在阅读的是哪一篇文章,这称为有状态;要么由客户端来记住状态,在请求的时候明确告诉服务端,如我正在阅读某某文章,现在要读它的下一篇,这称为无状态。

·转移(Transfer)

无论状态是由服务端还是由客户端来提供,“取下一篇文章”这个行为逻辑只能由服务端来提供,因为只有服务端拥有该资源及其表征形式。服务端通过某种方式,把“用户当前阅读的文章”转变成“下一篇文章”,这就被称为“表征状态转移”。

这篇关于访问远程服务-RPC 与 REST的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!