如何设计一个RPC框架


如何设计一个 RPC 框架?

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

什么是 RPC ?

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

RPC 架构

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

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

RPC 调用过程

RPC 调用过程

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

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

RPC 调用过程时序图

RPC 框架涉及技术

建立通信

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

网络传输

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

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

服务寻址

  • 服务注册:服务提供者启动后主动把服务注册到服务中心,注册中心(如 ZooKeeper 、Eureka)存储了该服务的 IP 、端口、调用方式(协议、序列化方式)等信息。
  • 服务发现:服务消费者第一次调用服务时,会通过注册中心找到相应的服务提供方地址列表,并缓存到本地,以供后续使用。当消费者再次调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从 IP 列表中取一个服务提供者的服务器调用服务。

服务调用

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

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

  • 既然系统采用分布式架构,那一个服务势必会有多个实例,要解决如何获取实例的问题?
  • 如何选择实例呢?就要考虑负载均衡。
  • 如果每次都去注册中心查询列表,效率很低,那么就要加缓存。
  • 客户端总不能每次调用完都等着服务端返回数据,所以就要支持异步调用。
  • 服务端的接口修改了,老的接口还有人在用,这就需要版本控制。
  • 服务端总不能每次接到请求都马上启动一个线程去处理,于是就需要线程池。
  • 容器支持,如 Kubernetes 、Docker 等。

简易的回答思路

  • 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 Zookeeper 来做,对吧。
  • 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。
  • 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。
  • 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。
  • 接着找到一台机器,就可以跟它发送请求了,第一个问题咋发送?你可以说用 Netty 了,NIO 方式;第二个问题发送啥格式数据?你可以说用 hessian 序列化协议了,或者是别的,对吧。然后请求过去了。
  • 服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码,对吧。

文章作者: wenjun
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 wenjun !
评论
 上一篇
Java线程池学习总结 Java线程池学习总结
Java 线程池学习总结一 使用线程池的好处 池化技术想必大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。 线程池提供了一种限制和管理资
2020-04-16
下一篇 
Java并发容器总结 Java并发容器总结
一 JDK 提供的并发容器总结JDK 提供的这些容器大部分在 java.util.concurrent 包中。 ConcurrentHashMap: 线程安全的 HashMap。 CopyOnWriteArrayList: 线程安全的 L
2020-04-16
  目录