RPC 通信
对于单独部署,独立运行的微服务实例而言,在业务需要时,需要与其他服务进行通信,这种通信方式是进程之间的通讯方式(inter-process communication,简称IPC)。
前文已经描述过,IPC有两种实现方式,分别为:同步过程调用、异步消息调用。在同步过程调用的具体实现中,有一种实现方式为RPC通信方式,远程过程调用(英语:Remote Procedure Call,缩写为 RPC)。
远程过程调用(英语:Remote Procedure Call,缩写为RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用,例:Java RMI。简单地说就是能使应用像调用本地方法一样的调用远程的过程或服务。很显然,这是一种client-server的交互形式,调用者(caller)是client,执行者(executor)是server。典型的实现方式就是request–response通讯机制。
RPC 实现步骤
一个正常的RPC过程可以分为一下几个步骤:
- 1、client调用client stub,这是一次本地过程调用。
- 2、client stub将参数打包成一个消息,然后发送这个消息。打包过程也叫做marshalling。
- 3、client所在的系统将消息发送给server。
- 4、server的的系统将收到的包传给server stub。
- 5、server stub解包得到参数。 解包也被称作 unmarshalling。
- 6、server stub调用服务过程。返回结果按照相反的步骤传给client。
在上述的步骤实现远程接口调用时,所需要执行的函数是存在于远程机器中,即函数是在另外一个进程中执行的。因此,就带来了几个新问题:
- 1、Call ID映射。远端进程中间可以包含定义的多个函数,本地客户端该如何告知远端进程程序调用特定的某个函数呢?因此,在RPC调用过程中,所有的函数都需要有一个自己的ID。开发者在客户端(调用端)和服务端(被调用端)分别维护一个{函数<-->Call ID}的对应表。两者的表不一定完全相同,但是相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,调用者通过映射表查询想要调用的函数的名称,找到对应的Call ID,然后传递给服务端,服务端也通过查表,来确定客户端所需要调用的函数,然后执行相应函数的代码。
- 2、序列化与反序列化。客户端如何把参数传递给远程调用的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
- 3、网络传输。远程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传递给服务端,然后在把序列化后的调用结果传回给客户端,完成这种数据传递功能的被成为传输层。大部分的网络传输成都使用TCP协议,属于长连接。
在上述步骤实现中,可以看到其中有对传递的数据进行序列化和反序列化的操作,这就是我们本节内容开始要学习的内容:Protobuf。
简介
Google Protocol Buffer( 简称 Protobuf)是Google公司内部的混合语言数据标准,他们主要用于RPC系统和持续数据存储系统。
Protobuf应用场景
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
简单来说,Protobuf的功能类似于XML,即负责把某种数据结构的信息,以某种格式保存起来。主要用于数据存储、传输协议等使用场景。
为什么已经有了XLM,JSON等已经很普遍的数据传输方式,还要设计出Protobuf这样一种新的数据协议呢?
Protobuf 优点
- 性能好/效率高
- 时间维度:采用XML格式对数据进行序列化时,时间消耗上性能尚可;对于使用XML格式对数据进行反序列化时的时间花费上,耗时长,性能差。
- 空间维度:XML格式为了保持较好的可读性,引入了一些冗余的文本信息。所以在使用XML格式进行存储数据时,也会消耗空间。
整体而言,Protobuf以高效的二进制方式存储,比XML小3到10倍,快20到100倍。
- 代码生成机制
- 代码生成机制的含义
在Go语言中,可以通过定义结构体封装描述一个对象,并构造一个新的结构体对象。比如定义Person结构体,并存放于Person.go文件:
type Person struct{
Name string
Age int
Sex int
}
在分布式系统中,因为程序代码时分开部署,比如分别为A、B。A系统在调用B系统时,无法直接采用代码的方式进行调用,因为A系统中不存在B系统中的代码。因此,A系统只负责将调用和通信的数据以二进制数据包的形式传递给B系统,由B系统根据获取到的数据包,自己构建出对应的数据对象,生成数据对象定义代码文件。这种利用编译器,根据数据文件自动生成结构体定义和相关方法的文件的机制被称作代码生成机制。 - 代码生成机制的优点 首先,代码生成机制能够极大解放开发者编写数据协议解析过程的时间,提高工作效率;其次,易于开发者维护和迭代,当需求发生变更时,开发者只需要修改对应的数据传输文件内容即可完成所有的修改。
- 代码生成机制的含义
- 支持“向后兼容”和“向前兼容”
- 向后兼容:在软件开发迭代和升级过程中,"后"可以理解为新版本,越新的版本越靠后;而“前”意味着早起的版本或者先前的版本。向“后”兼容即是说当系统升级迭代以后,仍然可以处理老版本的数据业务逻辑。
- 向前兼容:向前兼容即是系统代码未升级,但是接受到了新的数据,此时老版本生成的系统代码可以处理接收到的新类型的数据。
支持前后兼容是非常重要的一个特点,在庞大的系统开发中,往往不可能统一完成所有模块的升级,为了保证系统功能正常不受影响,应最大限度保证通讯协议的向前兼容和向后兼容。
- 支持多种编程语言 Protobuf不仅仅Google开源的一个数据协议,还有很多种语言的开源项目实现。在Google官方发布的Protobuf的源代码中包含了C++、Java、Python三种语言。本系列课程中,我们学习如何实现Golang语言中的功能实现。
Protobuf 缺点
- 可读性较差
为了提高性能,Protobuf采用了二进制格式进行编码。二进制格式编码对于开发者来说,是没办法阅读的。在进行程序调试时,比较困难。 - 缺乏自描述 诸如XML语言是一种自描述的标记语言,即字段标记的同时就表达了内容对应的含义。而Protobuf协议不是自描述的,Protobuf是通过二进制格式进行数据传输,开发者面对二进制格式的Protobuf,没有办法知道所对应的真实的数据结构,因此在使用Protobuf协议传输时,必须配备对应的proto配置文件。