远程函数调用(RPC-Remote Procedure Calls)
分布式计算中,远程过程调用(英语:Remote Procedure Call,RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种服务器-客户端(C/S)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。
RPC需要解决的问题:
- 函数映射
- 数据转换成字节流
- 网络传输
RPC概念模型
理论模型:分为调用端和被调用端。首先User发起一个本地调用,调用User-stub将参数进行打包,然后RPCRuntime将打包后的通过网络传递给被调用端的RPCRuntime,RPCRuntime接收完后交给Server-stub来解压数据,然后调用真正的业务逻辑Server并处理,将处理完的返回的结果进行打包通过网络传输传递到调用端RPCRuntime,然后将结果解压出来并返回。
IDL(Interface description language)文件
IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信。
生成代码
通过编译器工具把IDL文件转换成语言对应的静态库
编解码
从内存中表示到字节序列的转换称为编码,反之为解码,也常叫做序列化和反序列化。
通信协议
规范了数据在网络中的传输内容和格式。除必须的请求和响应数据外,通常还会包含额外的元数据。
网络传输
通常基于成熟网络库走TCP/IP传输
RPC的好处:
- 单一职责,有利于分工协作和运维开发
- 可扩展性强,资源使用率更优
- 故障隔离,服务的整体可靠性更高
后续内容待补😭
GRPC
微服务
单体架构
- 一旦某个服务宕机,会引起整个应用不可用,隔离性差
- 只能整体应用进行伸缩,浪费资源,可伸缩性差
- 代码耦合在一起,可维护性差
微服务架构:解决了单体架构的弊端
同时引入了新的问题
- 代码冗余
- 服务和服务之间存在调用关系
服务拆分后,服务和服务之间发生的是进程和进程之间的调用,服务器和服务器之间的调用。
那么就需要发起网络调用,网络调用我们能立马想起的就是http,但是在微服务架构中,http虽然方便,但性能较低,这时候就需要引入RPC(远程过程调用),通过自定义协议发起TCP调用,来加快传输效率。
RPC的全称是Remote Procedure Call,远程过程调用。这是一种协议,是用来屏蔽分布式计算的各种调用细节,使得你可以像是本地调用直接调用一个远程的函数。
客户端与服务端沟通的过程
- 客户端发送数据(以字节流的方式)
- 服务端接收并解析。根据约定要执行什么。然后把结果返回给客户
RPC:
- RPC就是将上述过程封装下,使其操作更加优化
- 使用一些大家都认可的协议使其规范化
- 做成一些框架。直接或间接长生利益
gRPC: A high-performance, open-source universal RPC framework
gRPC是一个高性能的、开源的通用的RPC框架
在gRPC中,我们称调用方为Client,被调用方为Server。和其它的RPC框架一样,gRPC也是基于“服务定义”的思想。简单的来讲,就是我们通过某种方式来描述一个服务,这种描述方式是语言无关的。在这个“服务定义”的过程中,我们描述了我们提供的服务的服务名是什么,有哪些方法可以被调用,这些方法有什么样的入参,有什么样的回参。
也就是说,在定义好了这些服务,这些方法之后,gRPC会屏蔽底层的细节,Client只需要直接调用定义好的方法,就能拿到预期的返回结果。对于Server端来说,还需要实现我们定义的方法。同样的,gRPC也会帮我们屏蔽底层的细节,我们只需要实现所定义的方法的具体逻辑即可。
gRPC还是语言无关的,支持C++作为服务端,使用Go、Java等作为客户端。为了实现这一点,我们在“定义服务”和在编码和解码的过程中,应该是做到语言无关的。
因此,gRPC使用了Protocol Buffers。这是谷歌开源的一套成熟的数据结构序列化机制。
序列化:将数据结构或对象转换成二进制串的过程
反序列化:将在序列化过程中所产生的二进制串转换成数据结构或者对象的过程
protobuf是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为protobuf是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。
优势:
- 序列化后体积相比JSON和XML很小,适合网络传输
- 支持跨平台多语言
- 消息格式升级和兼容性还不错
- 序列化和反序列化速度很快
proto文件介绍
message:protobuf中定义一个消息类型式是通过关键字message字段指定的。消息就是需要传输的数据格式的定义。
message关键字类似于C++中的class,Java中的class,go中的struct
在消息中承载的数据分别对应于每一个字段,其中每个字段都有一个名字和一种类型
一个proto文件中可以定义多个消息类型
字段规则:
- required:消息体中必填字段,不设置会导致编码异常。在protobuf2中使用,在protobuf3中被删去
- optional:消息体中可选字段。protobuf3没有了required,optional等说明关键字,都默认为optional
- repeated:消息体中可重复字段,重复的值的顺序会被保留在go中重复的会被定义为切片
消息号:
在消息体的定义中,每个字段都必须要有一个唯一的标识号,标识号是[1, 2^29 - 1]范围内的一个整数。
嵌套消息:
可以在其他消息类型中定义、使用消息类型,在下面的例子中,person消息就定义在PersonInfo消息内如:
message PersonInfo{
message Person{
string name = 1;
int32 height = 2;
repeated int32 weight = 3;
}
repeated Person info = 1;
}
如果要在它的父消息类型的外部重用这个消息类型,需要PersonInfo.Person的形式使用它,如:
message PersonMessage{
PersonInfo.Person info = 1;
}
服务定义:
如果想要将消息类型用在RPC系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。
service SearchService{
# rpc 服务函数名 (参数) 返回 (返回参数)
rpc Search(SearchRequest) returns (SearchResponse) {}
}
上述表示定义了一个RPC服务,该方法接受SearchRequest返回SearchResponse