RPC框架简析 | 青训营

146 阅读13分钟

一、RPC的基本概念

1.1 远程函数调用

在网上商城的支付过程中,我们怎么告诉支付服务我们要调用付款这个函数,而不是退款或者充值呢?在本地调用中,函数体是直接通过函数指针来指定的,我们调用哪个方法,编译器就自动帮我们调用它相应的函数指针。但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以,函数都有自己的一个ID, 在做RPC的时候要附上这个ID,还得有个ID和函数的对照关系表,通过ID找到对应的函数并执行。

image.png

RPC(Remote Procedure Call)是一种计算机通信协议,用于使远程计算机之间能够通过网络进行函数调用或方法调用。它的目标是使得在不同计算机上运行的程序能够像本地调用一样进行通信,以实现分布式系统中的协作。

在RPC中,客户端应用程序通过调用远程服务的函数或方法来请求执行某个操作,而远程服务则接收到请求后执行相应的操作,并将结果返回给客户端。

RPC的工作原理比较简单:客户端从本地调用远程服务的函数,然后将参数和调用请求打包成消息,并通过网络发送给远程服务。远程服务接收到消息后,解析参数和调用请求,并执行对应的操作。最后,远程服务将执行结果打包成消息发送回客户端,客户端收到结果后进行解析并继续执行。

RPC协议通常隐藏了底层网络细节,使得开发人员可以像调用本地函数一样调用远程函数,这样简化了分布式系统的开发过程。同时,RPC还提供了一系列的特性,如参数传递、序列化、错误处理和身份验证等,以确保可靠性、安全性和可扩展性。

1.2 RPC的概念模型

image.png RPC(Remote Procedure Call)的概念模型包括以下几个核心组件:

  1. 客户端(Client):客户端是发起RPC请求的一方。它调用本地的stub(代理对象),以本地函数调用的方式发送请求给远程服务。
  2. 服务端(Server):服务端是接收RPC请求并执行相应操作的一方。它提供了实际的服务实现,并等待来自客户端的请求。
  3. Stub(代理对象):Stub是客户端的本地代理对象,用于封装远程调用的细节。客户端通过调用Stub的方法来发起RPC请求,Stub负责将请求打包并发送给服务端。
  4. 序列化(Serialization):序列化是指将数据结构或对象转换为字节流的过程,以便在网络上传输。在RPC中,客户端和服务端之间需要对请求参数和返回结果进行序列化和反序列化,以便能够正确传递和解析数据。
  5. 传输层(Transport Layer):传输层负责实际的数据传输,将序列化后的请求消息从客户端发送到服务端,并将序列化后的结果消息从服务端返回给客户端。传输层可以基于不同的协议,如HTTP、TCP、UDP等。
  6. 注册中心(Registry):对于分布式系统来说,注册中心通常用于服务发现和服务管理。它可以记录可用的服务节点信息,并提供查找、注册和注销服务的功能,以便客户端能够找到合适的服务端进行RPC调用。
  7. 错误处理(Error Handling):RPC框架通常提供了错误处理机制,用于处理网络故障、超时、连接中断等异常情况。客户端和服务端都需要能够适当地处理错误,并返回相应的错误信息给调用方。

1.3 一次完整RPC的步骤

image.png

  1. IDL (Interface description language)文件:IDL通过-种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信
  2. 生成代码:通过编译器I具把IDL文件转换成语言对应的静态库
  3. 编解码:从内存中表示到字节序列的转换称为编码,反之为解码,也常叫做序列化和反序列化
  4. 通信协议:规范了数据在网络中的传输内容和格式。除必须的请求/响应数据外,通常还会包含额外的元数据
  5. 网络传输:通常基于成熟的网络库走TCP/UDP 传输

1.4 RPC的好处

  1. 单一职责,有利于分工协作和运维开发:RPC允许将系统拆分为不同的服务,每个服务专注于完成特定的任务,实现了单一职责原则。这样可以使团队成员更好地分工协作,每个团队负责开发和维护自己负责的服务,提高开发效率和团队协作能力。
  2. 可扩展性强,资源使用率更优:由于RPC的服务是独立的,可以根据需求增加或减少服务的数量。当系统负载增加时,可以通过增加服务节点来扩展系统的处理能力。而且,由于每个服务只关注自己的业务逻辑,可以更精确地分配资源,提高资源的利用率。
  3. 故障隔离,服务的整体可靠性更高:由于RPC服务是独立的,当某个服务出现故障时,不会影响到其他服务的正常运行。这种故障隔离的设计使得整个系统具备更高的可靠性。同时,可以针对不同的服务进行监控和故障恢复,快速定位和解决问题,提高系统的稳定性和可用性。

二、分层设计

2.1 编解码层

在RPC通信过程中,客户端和服务端之间需要通过网络传输请求和响应消息。由于网络传输需要使用二进制数据,而对象在内存中一般以数据结构的形式存在,因此需要将对象转换为可传输的二进制数据流进行网络传输。这时编解码层的作用就显现出来。

编码层负责将请求消息或响应消息中的参数和调用信息进行序列化,将对象转换为可传输的二进制数据流。常见的编码格式包括JSON、Protocol Buffers、Thrift等。编码的过程需要将对象的属性逐个转换为字节流,并添加相应的元数据信息,以便在接收端能够正确反序列化。

解码层则负责将接收到的二进制数据流进行反序列化,将其转换为可操作的参数和调用信息。解码的过程与编码相反,需要根据预定的规则和元数据信息,将字节流恢复为对象的属性,并还原方法调用的相关信息。

编解码层在RPC框架中的作用非常重要。它能够将复杂的对象转换为二进制数据进行网络传输,并在接收端恢复为可操作的对象形式,实现客户端和服务端之间的通信。通过使用适当的编解码格式,还可以提供更高的效率、更小的数据包大小和更好的跨平台兼容性。

总结来说,编解码层是RPC框架中负责序列化和反序列化请求和响应消息的组件。它将对象转换为可传输的二进制数据流,并在接收端将二进制数据流还原为对象,实现客户端和服务端之间的通信。 image.png

编解码层的数据格式

另外,编解码层的数据格式包括:

  1. 语言特定的格式:许多编程语言都内建了将内存对象编码为字节序列的支持,例如Java有java.io. Serializable。语言特定编码格式:这种编码形式好处是非常方便,可以用很少的额外代码实现内存对象的保存与恢复,这类编码通常与特定的编程语言深度绑定,其他语言很难读取这种数据。如果以这类编码存储或传输数据,那你就和这门语言绑死在一起了。
  2. 文本格式:JSON、XML、 CSV等文本格式,具有人类可读性。文本格式具有人类可读性,数字的编码多有歧义之处,比如XML和CSV不能区分数字和字符串,JSON虽然区分字符串和数字,但是不区分整数和浮点数,而且不能指定精度,处理大量数据时,这个问题更严重了;没有强制模型约束,实际操作中往往只能采用文档方式来进行约定,这可能会给调试带来一些不便。 由于JSON在一些语言中的序列化和反序列化需要采用反射机制,所以在性能比较差;
  3. 二进制编码:具备跨语言和高性能等优点,常见有Thrift 的BinaryProtocol, Protobuf 等。

编解码层的选型

在选择编解码层时,主要考虑以下几个方面:

  1. 兼容性:支持自动增加新的字段,而不影响老的服务,这将提高系统的灵活度。不同的应用场景可能对编解码的需求有所不同。一些框架提供了灵活的选项,允许根据业务需求进行自定义序列化和反序列化的逻辑。这种灵活性可以提供更好的定制化和扩展性。
  2. 通用性:第一、技术层面,序列化协议是否支持跨平台、跨语言。如果不支持,在技术层面上的通用性就大大降低了。第二、流行程度,序列化和反序列化需要多方参与,很少人使用的协议往往意味着昂贵的学习成本;另一方面,流行度低的协议,往往缺乏稳定而成熟的跨语言、跨平台的公共包。
  3. 性能:编解码层的性能直接关系到整个系统的性能。选择高效的编解码格式和算法可以减少序列化和反序列化的开销,并减小网络传输的数据量。一般来说,二进制格式如Protocol Buffers、MessagePack等比JSON格式效率更高。

2.2 协议层

协议是双方确定的交流语义,比如:我们设计一个字符串传输的协议,它允许客户端发送一个字符串, 服务端接收到对应的字符串。这个协议很简单,首先发送一 个4字节的消息总长度, 然后再发送字节的字符集charset长度,接下来就是消息的payload,字符集名称和字符串正文。 image.png 特殊结束符:一个特殊字符作为每个协议单元结束的标示。过于简单,对于一个协议单元必须要全部读入才能够进行处理,除此之外必须要防止用户传输的数据不能同结束符相同,否则就会出现紊乱。 image.png

变长协议:一般都是自定义协议,有header和payload组成,会以定长加不定长的部分组成,其中定长的部分需要描述不定长的内容长度,使用比较广泛

image.png

协议层的协议构造

协议层的协议构造组成包括:

  1. LENGTH:数据包大小,不包含自身
  2. HEADER MAGIC:标识版本信息,协议解析时候快速校验
  3. SEQUENCE NUMBER:表示数据包的seqID,可用于多路复用,单连接内递增
  4. HEADER SIZE:头部长度,从第14个字节开始计算一直到PAYLOAD前
  5. PROTOCOL ID:编解码方式,有Binary和Compact两种
  6. TRANSFORM ID:压缩方式,如zlib和snappy
  7. INFO ID:传递一些定制的meta信息
  8. PAYLOAD:消息体 image.png

2.3 网络通信层

套接字编程中的客户端必须知道两个信息:服务器的IP地址,以及端口号。

以下是不同函数的功能和作用:

socket函数创建一个套接字, bind 将一个套接 字绑定到一个地址上。listen 监听进来的连接,backlog的含义比较复杂,它可以指定挂起的连接队列的长度,当客户端连接的时候,服务器可能正在处理其他逻辑而未调用accept接受连接,此时会导致这个连接被挂起,内核维护挂起的连接队列,backlog则指定这个队列的长度, accept函数从队列中取出连接请求并接收它,然后这个连接就从挂起队列移除。如果队列未满,客户端调用connect马上成功,如果满了可能会阻塞等待队列未满(实际上在Linux中测试并不是这样的结果)。Linux的backlog默认是128, 通常情况下我们也指定为128即可。 image.png

Sokets API

connect客户端向服务器发起连接,accept接收一个连接请求,如果没有连接则会一直阻塞直到有连接进来。 得到客户端的fd之后,就可以调用read, write函数和客户端通讯,读写方式和其他I/O类似。 read从fd读数据,socket默认是阻塞模式的,如果对方没有写数据,read会一直阻塞着; write写fd写数据,socket默认是阻塞模式的,如果对方没有写数据,write会一直阻塞着;

socket关闭套接字,当另一端socket关闭后,这一端读写的情况: 尝试去读会得到一个EOF,并返回0。 尝试去写会触发一个SIGPIPE信号,并返回-1和errno=EPIPE, SIGPIPE的默认行为是终止程序,所以通常我们应该忽略这个信号,避免程序终止。 如果这一端不去读写 ,我们可能没有办法知道对端的socket关闭了。

image.png

网络库

  1. 可以提供易用API:可以封装底层Socket API,连接管理和时间分发;
  2. 网络库的功能包括:网络库协定支持:tcp,udp和uds等,并且可以优雅的退出程序和比较简单的处理异常。
  3. 对于网络库的性能方面:可以使应用层buffer减少copy重复,相关应用有高性能定时器,对象池等。