深入浅出RPC框架 | 青训营笔记

203 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记

远程函数调用(RPC)

RPC要解决的问题:

函数映射、数据转换成字节流、网络传输

RPC概念模型

User发起本地调用,将参数打包,交给RPC Runtime,传输给被调用端,RPC Runtime接收并将参数解包,调用真正的业务逻辑。

一次RPC的完整过程

不知道对方的方法和参数,需要IDL文件来约定,双方都依赖同一份IDL文件

调用端通过生成代码,用编码器编码成字节流,通过通信协议,传输给对端。对端解码后由上层处理。

RPC的好处

  1. 单一职责:有利于分工协作和运维开发,用户通过网关,可以看视频,看直播、购物、账号、广告,一个服务由一个团队负责
  2. 可扩展性强:资源使用率高,双十一压力大,可以对直播间、购物进行扩容
  3. 故障隔离:某一个服务故障,不会引起所有服务崩溃

RPC带来的问题

  1. 服务宕机
  2. 调用过程中网络异常、如何保障消息可达
  3. 请求量突增导致服务无法及时处理

分层设计

编解码层-》协议层-》网络传输层

用户自己编写业务逻辑代码->通过代码生成工具把IDL文件转换成不同语言对应的lib代码,里面封装了编解码逻辑->框架编解码->协议->通信

生成代码可以看成编解码的一部分,因为里面往往封装了编解码逻辑。

编解码层

  1. 生成代码

  2. 数据格式

语言的特定格式:编程语言内建的将内存对象转换为字节序列

文本格式:JSON、XML、CSV等文本格式,具有可读性

二进制编码:跨语言和高性能,常有Thrift的BinaryProtocol,Protobuf等

  1. 二进制编码

  1. 选型

兼容性:支持自动增加新的字段、而不影响老的服务,提高系统灵活度

通用性:支持跨平台、跨语言

性能:从空间和时间两个维度考虑,编码后数据大小和编码耗费时长

协议层

编解码后还要添加一些特定的元数据

  1. 概念

  1. 协议构造

  1. 协议解析

从内存中读取指定一部分数据,读取magic number知道是什么类型的协议,读取编解码方式解码获得消息体提交上层

网络通信层

  1. Sockets API

  1. 网络库

提供易用API:封装了底层Socket API,连接管理和事件分发

功能:协议支持:TCP\UDP和UDS等,优雅退出、异常处理等

性能:应用层buffer减少copy,高性能定时器、对象池等

RPC框架关键指标

稳定性-保障策略

熔断:保护调用方,防止被调用的服务出现问题而影响整个链路

A-B-C:C出现问题,B响应超时,A频繁请求B,导致B堆积大量请求而宕机

限流:保护被调用包,防止大流量把服务压垮

超时控制:避免浪费资源在不可用节点上,服务不可调用就快速返回

稳定性-请求成功率

负载均衡:多个服务分摊压力

重试:请求失败,重试几次,多次都失败才是失败

稳定性-长尾请求

长尾请求:明显高于平均响应时间的那部分占比比较小的请求

PCT99:响应的耗时从小到大排列,处于99%位置的请求就是PCT99请求,后面的1%就可以视为长尾请求

提高长尾请求成功率:

Backup Request(备份请求)

t3时间根据PCt99就应该返回,没返回就重发,时间是t4,否则是t1+t2

稳定性-注册中间件

注册中间件保证稳定性

易用性

扩展性

观测性

log 日志

metric 监控面板看服务QPS,延迟

tracing 链路跟踪 排查问题,层层请求每个阶段耗时,把整个链路串起来

内置观测服务:用户配置,环境变量,线程有多少、用到的中间件,可以通过http观测当前RPC框架的使用情况

高性能

企业实践

Kitex-整体架构

自研网络库

go原生库无法感知连接状态,在使用连接池时,池中存在失效连接,影响连接池的复用。

原生库存在goroutine暴涨的风险,一个连接一个goroutine的模式,由于连接利用率低下,存在大量goroutine占用调度开销,影响性能。

自研网络库Netpoll

解决无法感知连接状态问题,引入epoll主动监听机制,感知连接状态

解决goroutine暴涨风险:建立goroutine池,复用goroutine

提升性能:引入Nocopy Buffer,向上层提供NoCopy的调用接口,编解码层面零拷贝

扩展性设计

网络库优化

调度优化:

epoll_wait在调度上的控制

gopool重用goroutine降低同时运行协程数

LinkBuffer:

读写并行无锁,支持nocopy的流式读写

高校扩缩容

Nocopy Buffer池化,减少GC

Pool

引入内存池和对象池,减少GC开销

编解码优化

Codegen

预计算并预分配内存,减少内存操作次数,包括内存分配和拷贝

inline减少函数调用次数和避免不必要的反射操作

自研Go语言实现的Thrift IDL解析和代码生成器,支持完善的Thrift IDL语法和语义检查,并支持了插件机制-Thriftgo

JIT

使用JIT编译技术改善用户体验的同时带来更强的编解码性能,减轻用户维护生成代码的负担

基于JIT编译技术的高性能动态Thrift编解码器-Frugal

合并部署

微服务过微,传输和序列化开销越来越大,将亲和性强的服务实例尽可能调度到同一个物理机,远程RPC调用优化为本地IPC调用。

\