这是我参与「第五届青训营 」伴学笔记创作活动的第26天。主要介绍了RPC框架的分层设计、性能指标以及企业优化实践。
RPC的基本概念
RPC需要解决的问题:
- 函数映射(函数ID与函数体的对应关系)
- 数据转换成字节流(参数如何传递,数据如何编码)
- 网络传输(保证网络层高效稳定的传输数据) RPC的概念模型:
RPC的过程由5个模型组成:User、User-Stub、RPC-Runtime、Server—Stub、Server,客户端的User发起本地调用,User-stub将参数进行打包(序列化)交给RPCRuntime来提交给服务端执行,服务端的RPCRuntime接收数据,经过Server-stub将参数解包然后调用Server的函数执行,执行结果通过完全对称的方式传递给客户端的User。
RPC框架涉及到的一些概念
- IDL文件: 来描述接口,使得不同平台上运行的对象和用不同语言编写的程序可以通信;
- 代码生成: 编译器将IDL文件转化成语言对应的静态库
- 编解码: 比如JSON等,也成序列化和反序列化
- 通讯协议: 数据在网络中传输的规范,包括请求响应和元数据
- 网络传输: 成熟网络库的TCP或UDP传输
RPC可能遇到的问题(RPC框架需要解决的问题):
- 服务宕机
- 网络异常
- 请求量突增
Thrift的分层设计
Thrift的分层如下图所示大致包括业务逻辑层、编解码层、协议层、网络通信层
编解码层
客户端和服务端会依赖于同一份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编码后如下:
可以看到对于string这种不定长的类型,会有8byte来表示实际长度,而向int类型的就没有,对于list类型的字段,会有2byte来表示list元素的类型,8byte来表示list的长度。
编码格式的选型 要考虑以下几个方面
- 兼容性:支持自动增加新的字段,不影响老的服务,提高系统的灵活度
- 通用性:支持跨平台,跨语言
- 性能:从空间(码长)和时间(编码时间)两个维度考虑,
协议层
协议的设计套路,一般有以下两个
- 特殊结束符:用指定的特殊字符来分割不同的信息单元
- 变长协议:由定长加不定长的部分组成,其中不定长的部分前面要用定长的部分来描述实际长度 Thrift的通信协议:
- 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,过程如下图所示:
设置一个阈值(一般为长尾请求的时间界限)t3,在t3内没返回的话再发送一个Request,以减少延时(不用等到第一次请求返回后再重试)。
框架中一般会通过注册中间件的方式实现稳定性,比如在创建Server或Client时加入一些option
WithTimeout(...)
WithRateLimiter(...)
WithLoadbalancer(...)
WithRetry(...)
WithBackupRequest(...)
WithCircuitBreaker(...)
易用性
- 保证开箱即用,有合理的默认参数选项和丰富的文档
- 周边工具:生成代码工具和手脚架工具
扩展性
可扩展的点有:
- Middleware
- Option
- 编解码层
- 协议层
- 网络传输层
- 代码生成工具插件扩展
观测性
- Log、Metric、Tracing
- 内置观测行服务
高性能
提升的目标为
- 高吞吐
- 低延迟 衡量时要考虑多个场景
- 单机多机
- 单连接多链接
- 单/多 client/server
- 不同大小的请求包
- 不同的请求类型:pingpong、streaming 常用的手段有
- 连接池
- 多路复用
- 高性能编解码协议
- 高性能网络库