RPC框架设计 | 青训营笔记

63 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第26天。主要介绍了RPC框架的分层设计、性能指标以及企业优化实践。

RPC的基本概念

RPC需要解决的问题:

  • 函数映射(函数ID与函数体的对应关系)
  • 数据转换成字节流(参数如何传递,数据如何编码)
  • 网络传输(保证网络层高效稳定的传输数据) RPC的概念模型:

image.png RPC的过程由5个模型组成:User、User-Stub、RPC-Runtime、Server—Stub、Server,客户端的User发起本地调用,User-stub将参数进行打包(序列化)交给RPCRuntime来提交给服务端执行,服务端的RPCRuntime接收数据,经过Server-stub将参数解包然后调用Server的函数执行,执行结果通过完全对称的方式传递给客户端的User。

RPC框架涉及到的一些概念

  1. IDL文件: 来描述接口,使得不同平台上运行的对象和用不同语言编写的程序可以通信;
  2. 代码生成: 编译器将IDL文件转化成语言对应的静态库
  3. 编解码: 比如JSON等,也成序列化和反序列化
  4. 通讯协议: 数据在网络中传输的规范,包括请求响应和元数据
  5. 网络传输: 成熟网络库的TCP或UDP传输

RPC可能遇到的问题(RPC框架需要解决的问题):

  • 服务宕机
  • 网络异常
  • 请求量突增

Thrift的分层设计

Thrift的分层如下图所示大致包括业务逻辑层、编解码层、协议层、网络通信层

image.png

编解码层

客户端和服务端会依赖于同一份IDL文件生成代码,生成的代码中封装了编解码的逻辑; IDL文件的数据格式分为以下几类:

  • 语言特定的格式
  • 文本格式(JSON、XML等,描述能力有限,不能区分浮点数和整数)
  • 二进制编码(Thrift的BinaryProtocol,Protobuf等;跨语言、高性能) 二进制编码TLV(Tag Length、Value)编码:
struct Person{
    1: reqired string        userName,
    2: optional i64          favoriteNumber,
    3: optional list<string> interests
}

以上述代码为例经过BinaryProtocol编码后如下:

image.png 可以看到对于string这种不定长的类型,会有8byte来表示实际长度,而向int类型的就没有,对于list类型的字段,会有2byte来表示list元素的类型,8byte来表示list的长度。

编码格式的选型 要考虑以下几个方面

  • 兼容性:支持自动增加新的字段,不影响老的服务,提高系统的灵活度
  • 通用性:支持跨平台,跨语言
  • 性能:从空间(码长)和时间(编码时间)两个维度考虑,

协议层

协议的设计套路,一般有以下两个

  • 特殊结束符:用指定的特殊字符来分割不同的信息单元
  • 变长协议:由定长加不定长的部分组成,其中不定长的部分前面要用定长的部分来描述实际长度 Thrift的通信协议:

image.png

  • LENGTH:表示数据包大小,不包含自身
  • HEADER MAGIC:表示版本信息,协议解析时快速校验
  • SEQUENCE NUMBER:表示数据包的seqID,可用于多路复用,单连接内递增
  • HEADER SIZE:头部长度,动第14字节开始计算到PAYLOAD前
  • PROTOCOL ID:编解码方式,有Binary和Compact两种
  • TRANSFORM ID:压缩方式,如zlib和snappy
  • INFO ID:传递一些定制的meta信息
  • PAYLOAD:消息体

协议解析: 先读取MagicNumber,了解到协议版本,再读取PayloadCodec了解到编码方式,最后解码Payload。

网络通信层

Sockets API: 介于应用层和传输层之间的接口,来封装TCP/UDP消息。 网络库:

  • 提供易用API:封装底层Socket API,连接管理和事件分发
  • 功能:协议支持TCP、UDP、UDS等,优雅退出、异常处理等
  • 性能:应用层buffer减少copy、高性能定时器、对象池等

RPC框架的关键指标

稳定性

需要一些保障策略:

  • 熔断:保护调用方,防止被调用的服务出现问题而影响到整个链路
  • 限流:保护被调用方,防止大流量把服务压垮
  • 超时控制:避免浪费资源在不可用节点上

保证请求成功率,提高成功率的方式有以下两种:

  • 负载均衡
  • 重试(合理的重试策略,防止链路过长导致雪崩)

长尾请求的处理:要提高其成功率,一种方式为Backup Request,过程如下图所示:

image.png

设置一个阈值(一般为长尾请求的时间界限)t3,在t3内没返回的话再发送一个Request,以减少延时(不用等到第一次请求返回后再重试)。

框架中一般会通过注册中间件的方式实现稳定性,比如在创建Server或Client时加入一些option

WithTimeout(...)
WithRateLimiter(...)
WithLoadbalancer(...)
WithRetry(...)
WithBackupRequest(...)
WithCircuitBreaker(...)

易用性

  • 保证开箱即用,有合理的默认参数选项和丰富的文档
  • 周边工具:生成代码工具和手脚架工具

扩展性

可扩展的点有:

  • Middleware
  • Option
  • 编解码层
  • 协议层
  • 网络传输层
  • 代码生成工具插件扩展

观测性

  • Log、Metric、Tracing
  • 内置观测行服务

高性能

提升的目标为

  • 高吞吐
  • 低延迟 衡量时要考虑多个场景
  • 单机多机
  • 单连接多链接
  • 单/多 client/server
  • 不同大小的请求包
  • 不同的请求类型:pingpong、streaming 常用的手段有
  • 连接池
  • 多路复用
  • 高性能编解码协议
  • 高性能网络库