谁能用通俗的语言解释一下什么是 RPC 框架?

689 阅读4分钟

首先说下什么是RPC?RPC全称Remote Procedure Call,即远程过程调用。

RPC的主要目标是让构建分布式计算(应用)更容易、透明,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC框架需提供一种透明调用机制,让使用者不必显式的区分本地调用和远程调用。

RPC框架负责屏蔽底层的传输方式(TCP或者UDP)、序列化方式(XML/JSON/二进制)和通信细节。

我在面试中常常会问:既然已经有http协议了,为什么我们还需要RPC方式来实现分布式系统?

(更多大厂RPC常考内容可以来看我讲的《分布式RPC服务框架精讲》,从零开始快速掌握RPC框架工作原理和实操技巧)

这里就要说到RPC最大的优势:易用性。使用http还需要搭配web服务,RPC可以避免这些问题,无感。

如果没有RPC协议的封装,我们将面临下面的困境:

  • 以一种对方能够理解的协议构建请求包
  • 将这个请求包设法发送到对方机器的进程上
  • 对方机器根据协议理解请求包的内容
  • 对方机器处理请求,并以客户机能够理解的协议构造回复包
  • 想办法将这个会风暴,发送到请求包来源的远程机器上的具体进程,并设法把请求包和回复包关联起来

典型的RPC框架介于传输层和应用中间,可以帮助处理以下问题:

  • 可靠性:“吃掉”传输层遇到的错误
  • 平台无关性:比如Windows平台是否可以和Linux平台进行通讯?64位得系统是否可以和32位系统进行通讯?
  • 服务发现和路由选择:RPC调用实际上是对某个服务的调用,那么RPC框架需要解决具体调用需要落到哪台机器的哪个进程上。
  • 消息分发:一般一个进程上会提供多种RPC调用,RPC框架需要提供区别不同类型RPC消息并转到相应处理函数上。

然后说下RPC框架的核心部分:

  • RPC接口

因为RPC的作用:让使用者调用远程请求时,就像调用本地函数

所以不管是本地客户还是远程服务端,需要使用一套统一的接口,然后两边分别实现自己的逻辑。

举例:设计一个获取部门员工列表的RPC请求,接口可能如下设计,传入一个部门ID,返回部门成员的列表:

viod getEmployeeList(const DepartmentID &id, EmployeeList &list);

然后在客户端部分根据接口可以这样实现:

clientVoidGetEmployeeList(const DepartmentID &id, EmployeeList &list){
    auto result = Transport.send(GET_EMPLOYEE_LIST, encode(id));
    list.decode(result.buffer());
}

接口的作用:告诉客户端你只能这样调用这个RPC,同时告诉服务器客户端那边只会这样调用。

具体RPC实现者只需要关心接口和服务端的实现逻辑。一般RPC框架会提供友好的封装,最简单的形式:只需要实现一个函数。函数的签名就是接口,实现就是服务端的代码,客户端的代码则由框架自动填充

  • 对象序列化和反序列化

对象序列化的方式有很多种,比如比较通用的JSON、Protocol Buffers等,也可以按照自己的需求自己定制序列化和反序列化方式。

  • 传输协议和传输层

用得比较多的比如TCP、UDP这类的,也有为了追求性能选择RDMA/RoCE/DPDK, 或者想gRPC那样更上层的HTTP2。

传输层的选择需要考虑几个因素:

  1. 物理限制,有些协议需要在特定的硬件环境中才能运行
  2. 传输特性,比如TCP协议对可靠性有一定保证,但需要用户自己处理黏包问题
  3. 性能、安全性、是否好调试等因素也可以进行参考
  • RPC消息分发

当服务端收到一个RPC消息后,需要根据RPC类型和相应规则分发请求到相应的处理函数上。

常见的做法:

  1. 暴力switch/if else,由编译器做这方面的优化
  2. 实现查找表,以RPC类型(常见为枚举类型)作为下标定位到处理函数
  3. HashMap,这个方式最为灵活,前两种方案的实现都有一定的局限性

关于RPC框架和分布式计算问题处理,以及RPC框架设计实现的内容,可以来免费试听《大厂常考分布式RPC服务框架精讲》。