作者:zj5415 | 来源:互联网 | 2023-02-04 13:05
1 RPC协议综述:ONC RPC为例
RPC需要解决五个问题:如何规定远程调用的语法?如果传递参数?如何表示数据?(前三个问题统称为协议问题)如何知道一个服务端都实现了哪些远程调用,从哪个端口可以访问这个远程调用?(服务发现问题)发生了错误、重传、丢包、性能等问题怎么办?(传输问题)
语法问题:RPC调用标准如下
比如基于RPC协议实现的NFS(Network File System,网格文件系统)可以在本地mount一个远程的目录到本地的一个目录,从而使得本地的用户在这个目录里面写入、读出任何文件的时候,其实操作的是远程另一台机器上的文件。
表示数据问题:XDR(External Data Representation,外部数据表示法)是一个标准的数据压缩格式,可以表示基本的数据类型,也可以表示结构体,用于编码和解码参数:
传递参数问题:在 RPC 的调用过程中,所有的数据类型都要封装成类似的格式。而且 RPC 的调用和结果返回,也有严格的格式:
为了可以成功调用 RPC,在客户端和服务端实现 RPC 的时候,首先要定义一个双方都认可的程序、版本、方法、参数等,格式类似上图;
有了协议定义文件,ONC RPC 会提供一个工具,根据这个文件生成客户端和服务器端的 Stub 程序,此程序最下层的是 XDR 文件,用于编码和解码参数,XDR文件是客户端和服务端共享的,因为只有双方一致才能成功通信;
最终的通信:客户端通过Stub的函数来调用RPC类库来真正发生请求,服务端将结果返回服务端的 Stub,服务端 Stub 程序发送结果给客户端,客户端的 Stub 程序正在等待结果,当结果到达客户端 Stub,就将结果返回给客户端的应用程序,从而完成整个调用过程。
- 传输问题可以通过类库解决
- 服务发现问题可以通过注册中心来解决
如下为内嵌在服务端的类似注册中心的portmapper:
当然也可以独立出来
2 基于XML的SOAP
原先的二进制RPC有很多缺点:格式要求严格,修改过于复杂,不面向对象,因此产生了基于文本的调用方式—基本XML的SOAP
SOAP为简单对象访问协议(Simple Object Access Protocol),使用XML编写简单的请求和回复消息,并用HTTP协议进行传输,多为POST
SOAP将请求和回复放在一个信封里面,就像传递一个邮件一样。信封里面的信分抬头和正文。
使用Web服务描述语言,即WSDL(Web Service Description Languages)。它也是一个XML文件,客户端根据WSDL来调用,有工具可以根据WSDL生成客户端Stub,让客户端通过Stub进行远程调用,就跟调用本地的方法一样。
UDDI(Universal Description, Discovery, and Integration),即统一描述、发现和集成协议。
它是一个注册中心,服务提供方可以将WSDL描述文件发布到这个注册中心,注册完毕后,服务使用方可以查找到服务的描述,封装为本地的客户端进行调用。
3 基于JSON的RESTFUL
SOAP过于复杂,而且设计是面向动作的,因而往往因为架构问题导致并发量上不去,因此用在企业内部中,互联网应用一般使用RESTFUL
xml格式改为使用Json来表示,且HTTP除了POST,把PUT、DELETE、GET等方法也使用上
以资源为核心,而不是以过程为核心,按照这种设计模式,RESTful API和SOAP API都可以将架构实现成无状态的,面向资源的、幂等的、横向扩展的、可缓存的。
但是SOAP的XML正文中,是可以放任何动作的。
例如XML里面可以写,等。这就方便使用SOAP的人,将大量的动作放在API里面。
RESTful正文里的JSON基本描述的就是资源的状态,没办法描述动作,而且能够出发的动作只有CRUD,也即POST、GET、PUT、DELETE,也就是对于状态的改变,所以,从接口角度就阻止了动作;
不过也有很多技巧的方法,在使用RESTful API的情况下,依然提供基于动作的有状态请求,这就属于反模式了。
如Eureka、zookeeper,通过注册中心的方式来实现
4 二进制类RPC协议
文本的最大问题是占用字节数目比较多,因而对于数据中心内部的相互调用,一般采用更加省空间和带宽的二进制的方案,如Dubbo的二进制的RPC方式,默认使用Hession2,这是自描述的协议,综合了XML和二进制的优势;
通信方式和标准RPC类似:
当并发量越来越大,已经到了微服务的阶段的时候,同SOA不同,微服务粒度更细,模块之间的关系更加复杂。(可见微服务和微内核架构一文)
如上图架构,如果使用二进制的方式进行序列化,虽然不用协议文件来生成Stub,但是对于接口的定义,以及传的对象DTO,还是需要共享JAR。因为只有客户端和服务端都有这个JAR,才能成功地序列化和反序列化。
但当关系复杂的时候,JAR的依赖也变得异常复杂,难以维护,而且如果在DTO里加一个字段,双方的JAR没有匹配好,也会导致序列化不成功,而且还有可能循环依赖。
这个时候,一般有两种选择。第一种,建立严格的项目管理流程:
不允许循环调用,不允许跨层调用,只准上层调用下层,不允许下层调用上层。
接口要保持兼容性,不兼容的接口新添加而非改原来的,当接口通过监控,发现不用的时候,再下掉。
升级的时候,先升级服务提供端,再升级服务消费端。
第二种,改用RESTful的方式:
使用Spring Cloud,消费端和提供端不用共享JAR,各声明各的,只要能变成JSON就行,而且JSON也是比较灵活的。
使用RESTful的方式,性能会降低,所以需要通过横向扩展来抵消单机的性能损耗。
Dubbo是SOA时代的产物,底层使用Netty之类的NIO框架,基于TCP协议传输,配合以Hession序列化完成RPC通信。
SpringCloud基于Http协议+Restful接口调用实现远程过程的通信。
相对来说,Http请求会有更大的报文,占的带宽也会更多。但是REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适,至于注重通信速度还是方便灵活性,具体情况具体考虑。
5 跨语言类RPC协议
要求:
传输性能:服务之间的调用如此频繁了,还是二进制的越快越好。
跨语言:服务多了,什么语言写成的都有,而且不同的场景适宜用不同的语言,不能一个语言走到底。
最好既严谨又灵活,添加个字段不用重新编译和发布程序。
最好既有服务发现,也有服务治理,就像Dubbo和Spring Cloud一样。
GRPC正在努力完成这四个要求,,其二进制序列化协议是Protocol Bufers
GRPC序列化使用Protocol Bufers,网络传输使用HTTP 2.0,服务治理可以使用基于Envoy的Service Mesh。
Service Mesh:可以将应用之间的调用全部由中间层代理,服务之间的治理也是,到平台层解决,就成了Service Mesh,如GRPC的的Envoy: