如何设计一个 RPC 框架?

Scroll Down

如何设计一个 RPC 框架可以说是面试时的常客了,下面我就简要谈谈自己的看法。

什么是 RPC ?

RPC(Remote Procedure Call)——远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求当然可以,但是可能会比较慢而且一些优化做的并不好。RPC 的出现就是为了解决这个问题。

RPC 架构

一个完整的 RPC 架构里面包含了四个核心的组件,分别是 Client、Client Stub、Server 以及 Server Stub ,这个 Stub 可以理解为存根。

客户端(Client):服务的调用方。
客户端存根(Client Stub):存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
服务端(Server):真正的服务提供者。
服务端存根(Server Stub):接收客户端发送过来的消息,将消息解包,并调用本地的方法。

服务消费方(client)调用以本地调用方式调用服务;

  • client stub 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
  • client stub 找到服务地址,并将消息发送到服务端;
  • server stub 收到消息后进行解码;
  • server stub 根据解码结果调用本地的服务;
  • 本地服务执行并将结果返回给 server stub ;
  • server stub 将返回结果打包成消息并发送至消费方;
  • client stub 接收到消息,并进行解码;

服务消费方得到最终结果。
image.png

下面再贴一个网上的时序图:

image.png

RPC 框架涉及技术

  1. 建立通信

首先,要解决通讯的问题,主要是通过在客户端和服务器之间建立 TCP 连接,远程过程调用的所有交换的数据都在这个连接里传输。当前很多 RPC 框架都直接基于 Netty 这一 IO 通信框架,推荐使用 Netty 作为底层通信框架。

  1. 网络传输

数据传输采用什么协议(二进制数据格式组织)?

数据该如何序列化和反序列化?( kryo / protobuf / protostuff / hessian / fastjson / …)

  1. 服务寻址

  2. 服务注册:服务提供者启动后主动把服务注册到服务中心,注册中心(如 ZooKeeper 、Eureka)存储了该服务的 IP 、端口、调用方式(协议、序列化方式)等信息。

  3. 服务发现:服务消费者第一次调用服务时,会通过注册中心找到相应的服务提供方地址列表,并缓存到本地,以供后续使用。当消费者再次调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从 IP 列表中取一个服务提供者的服务器调用服务。

  4. 服务调用
    服务消费者进行本地调用(通过动态代理)之后得到了返回值。实际上是在 Proxy 中封装了一系列的过程,包括序列化、请求服务提供者、反序列化等。

实现高可用 RPC 框架需要考虑到的问题

既然系统采用分布式架构,那一个服务势必会有多个实例,要解决如何获取实例的问题?

  1. 如何选择实例呢?就要考虑负载均衡。
  2. 如果每次都去注册中心查询列表,效率很低,那么就要加缓存。
  3. 客户端总不能每次调用完都等着服务端返回数据,所以就要支持异步调用。
  4. 服务端的接口修改了,老的接口还有人在用,这就需要版本控制。
  5. 服务端总不能每次接到请求都马上启动一个线程去处理,于是就需要线程池。
    容器支持,如 Kubernetes 、Docker 等。
简易的回答思路
  1. 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 Zookeeper 来做,对吧。
  2. 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。
  3. 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。
  4. 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。
  5. 接着找到一台机器,就可以跟它发送请求了,第一个问题咋发送?你可以说用 Netty 了,NIO 方式;第二个问题发送啥格式数据?你可以说用 hessian 序列化协议了,或者是别的,对吧。然后请求过去了。
  6. 服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码,对吧。

文章来源: 如何设计一个 RPC 框架