RPC协议

RPC 的全称是 Remote Procedure Call,即远程过程调用

简单解读字面意思,远程肯定是指要跨机器而非本机,所以需要用到网络编程才能实现,但是不是只要通过网络通信访问到另一台机器的应用程序,就可以称之为 RPC 调用了?

显然并不够, RPC 需要帮助我们屏蔽网络编程细节,实现调用远程方法就跟调用本地方法一样的体验,我们不需要因为这个方法是远程调用就需要编写很多与业务无关的代码。

RPC 的作用体现在这两个方面:

  1. 屏蔽网络编程细节,让我们感觉就是调用项目内的方法
  2. 隐藏底层网络通信的复杂性,让我们更专注于业务逻辑

序列化

网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。对象是肯定没法直接在网络中传输的,需要提前把它转成可传输的二进制数据,并且转换必须是可逆的,这个过程叫做“序列化”。

服务提供方再根据反序列化出来的请求对象找到对应的实现类,完成真正的方法调用,然后把执行结果序列化后,回写到对应的 TCP 通道里面。调用方获取到应答的数据包后,再反序列化成应答对象,这样调用方就完成了一次 RPC 调用。

JDK 自带的序列化机制:ObjectOutputStream

序列化过程就是在读取对象数据的时候,不断加入一些特殊分隔符,这些特殊分隔符用于在反序列化过程中截断,

  1. 头部数据用来声明序列化协议、序列化版本,用于高低版本向后兼容
  2. 对象数据主要包括类名、签名、属性名、属性类型及属性值,当然还有开头结尾等数据,除了属性值属于真正的对象值,其他都是为了反序列化用的元数据
  3. 存在对象引用、继承的情况下,就是递归遍历上图的写对象逻辑

实际上任何一种序列化框架,核心思想就是设计一种序列化协议,将对象的类型、属性类型、属性值一一按照固定的格式写到二进制字节流中来完成序列化,再按照固定的格式一一读出对象的类型、属性类型、属性值,通过这些信息重新创建出一个新的对象,来完成反序列化。

JSON

JSON 是典型的 Key-Value 方式,没有数据类型,是一种文本型序列化框架

JSON 进行序列化的额外空间开销比较大,对于大数据量服务这意味着需要巨大的内存和磁盘开销;

JSON 没有类型,但像 Java 这种强类型语言,需要通过反射统一解决,所以性能不会太好。

所以如果 RPC 框架选用 JSON 序列化,服务提供者与服务调用者之间传输的数据量要相对较小,否则将严重影响性能。

专注于业务接口

有什么办法来简化 API,屏蔽掉 RPC 细节,让使用方只需要关注业务接口,像调用本地一样来调用远程呢?

如果你了解 Spring,那你一定知道AOP,其核心由动态代理实现对方法进行拦截增强,以便于增加需要的额外处理逻辑。其实这个技术也可以应用到 RPC 场景

RPC 会自动给接口生成一个代理类,当我们在项目中注入接口的时候,运行过程中实际绑定的是这个接口生成的代理类。这样在接口方法被调用的时候,它实际上是被生成代理类拦截了,这样我们就可以在生成的代理类里面,加入远程调用逻辑。这样调用方在调用远程方法的时候就获得了像调用本地接口一样的体验。

服务发现

为了高可用,在生产环境中服务提供方都是以集群的方式对外提供服务,集群里面的这些 IP 随时可能变化,我们也需要用一本“通信录”及时获取到对应的服务节点,这个获取的过程我们一般叫作“服务发现”。

  1. 服务注册:在服务提供方启动的时候,将对外暴露的接口注册到注册中心之中,注册中心将这个服务节点的 IP 和接口保存下来。
  2. 服务订阅:在服务调用方启动的时候,去注册中心查找并订阅服务提供方的 IP,然后缓存到本地,并用于后续的远程调用。

为什么不能使用DNS来做服务发现:

  1. DNS 采取了多级缓存机制,一般配置的缓存时间较长,下线的IP不能及时移除
  2. 服务调用者不能及时感知到服务节点的变化,新上线的服务节点不能及时接收到流量

AP还是CP?

在RPC 框架中,在服务节点刚上线时,服务调用方是可以容忍在一段时间之后(比如几秒钟之后)发现这个新上线的节点的。毕竟服务节点刚上线之后的几秒内,甚至更长的一段时间内没有接收到请求流量,对整个服务集群是没有什么影响的,所以我们可以牺牲掉 CP(强制一致性),而选择 AP(最终一致),来换取整个注册中心集群的性能和稳定性。

最终我们可以考虑采用消息总线机制。注册数据可以全量缓存在每个注册中心内存中,通过消息总线来同步数据。当有一个注册中心节点接收到服务节点注册时,会产生一个消息推送给消息总线,再通过消息总线通知给其它注册中心节点更新数据并进行服务下发,从而达到注册中心间数据最终一致性,具体流程如下图所示:

  1. 当有服务上线,注册中心节点收到注册请求,服务列表数据发生变化,会生成一个消息,推送给消息总线,每个消息都有整体递增的版本。
  2. 消息总线会主动推送消息到各个注册中心,同时注册中心也会定时拉取消息。对于获取到消息的在消息回放模块里面回放,只接受大于本地版本号的消息,小于本地版本号的消息直接丢弃,从而实现最终一致性。
  3. 消费者订阅可以从注册中心内存拿到指定接口的全部服务实例,并缓存到消费者的内存里面。
  4. 采用推拉模式,消费者可以及时地拿到服务实例增量变化情况,并和内存中的缓存数据进行合并。

健康检测


RPC协议
http://example.com/post/RPC.html
作者
SamuelZhou
发布于
2022年12月18日
许可协议