RPC 框架 | 青训营

42 阅读3分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 14 天

基本概念

  • 相比本地函数调用,RPC调用需要解决的问题

    • 函数映射
    • 数据转换成字节流
    • 网络传输
  • 一次 RPC 的完整过程:

    1. 序列化/反序列化
    2. 协议编码/解码
    3. 网络模块通信
  • RPC 带来的问题将由 RPC 框架来解决

    • 服务宕机如何感知?

      • Healthcheck
    • 遇到网络异常应该如何应对?

      • 统一 Error-handling
    • 请求量暴增怎么处理?

      • 动态扩容

RPC 框架分层设计

编解码层

  • 数据格式

    • 语言特定格式:例如 java.io.Serializable
    • 文本格式:例如 JSON、XML、CSV 等
    • 二进制编码:常见有 Thrift 的 BinaryProtocol,Protobuf,实现可以有多种形式,例如 TLV 编码 和 Varint 编码
  • 选型考察点

    • 兼容性

    • 通用型

    • 性能

      • 空间开销
      • 时间开销
  • 生成代码和编解码层相互依赖,框架的编解码应当具备扩展任意编解码协议的能力

协议层

  • 以 Thrift 的 THeader 协议为例

image.png

LENGTH字段是32位,计算数据包中剩余的字节数,不包括长度字段。HEADER SIZE字段是16位,定义了除HEADER MAGIC、FLAGS、SEQUENCE NUMBER和HEADER SIZE字段外头部剩余部分的大小。头部大小字段以字节/4表示。

变换ID是变量。每个变换的数据由代码中的变换ID定义,头部没有给出大小。如果客户端指定了变换ID,服务器不知道该变换ID,则必须返回错误,因为我们不知道如何转换数据。

相反,info头部中的数据是可忽略的。这只应该是时间戳、调试跟踪等。使用头部大小,如果不知道info ID,您应该能够跳过此数据并安全读取有效负载。

Info ID应该按从旧到新的顺序支持,以便如果我们读取不支持的info ID,则其余的info ID也不会被支持,我们可以安全地跳到有效负载。

Info ID和变换ID应该共享相同的ID空间。

网络通信层

  • 阻塞 IO 下,耗费一个线程去阻塞在 read(fd) 去等待用足够多的数据可读并返回。
  • 非阻塞 IO 下,不停对所有 fds 轮询 read(fd) ,如果读取到 n <= 0 则下一个循环继续轮询。

第一种方式浪费线程(会占用内存和上下文切换开销),第二种方式浪费 CPU 做大量无效工作。而基于 IO 多路复用系统调用实现的 Poll 的意义在于将可读/可写状态通知和实际文件操作分开,并支持多个文件描述符通过一个系统调用监听以提升性能。 网络库的核心功能就是去同时监听大量的文件描述符的状态变化(通过操作系统调用),并对于不同状态变更,高效,安全地进行对应的文件操作。