这是我参与「第五届青训营 」笔记创作活动的第16天
只不过是字节给我的任务罢了
RPC
远程函数调用(Remote Procedure Calls)
需要解决的问题
- 函数映射,每个函数都有一个ID
- 数据转换成字节流,
- 网络传输
一次RPC的完整过程
- IDL文件,Interface Description Language 接口描述语言通过中立的方式描述接口,使得不同平台上运行的对象和用不同语言编写的程序可以相互通信
- 生成代码,通过编译工具把IDL文件转换为对应语言的静态库
- 编解码,从内存中表示到字节序列的转换称为编码,反之为解码,也叫做序列化和反序列化
- 通信协议,规范数据在网络中的传输内容和格式,除了必须的请求/响应数据,通常还包括额外的元数据
- 网络传输,通常基于成熟的网络库走TCP/UDP传输
RPC的好处
- 单一职责,利于分工协作和运维开发
- 可扩展性强,资源使用率更优
- 故障隔离,服务的整体可靠性更高
RPC框架
分层设计
-
编码层
-
客户端和服务端依赖于同一个IDL文件,生成为不同的代码
-
数据格式
-
语言特定的格式:很多编程语言都內建了将内存对象编码为字节序列的支持,如java.io.Serializable
-
文本格式:JSON、XML、CSV
-
二进制编码:BinaryProtocol、Protobuf等
-
TLV编码
- Tag,标签,可以理解为类型
- Length,长度
- Value,值,Value也可以是个TLV结构
-
选型
- 兼容性,支持自动增加新的字段,不影响老的服务
- 通用性,支持跨平台、跨语言
- 性能,从空间和时间两个维度考虑,也就是编码后数据大小和编码耗费时长
-
-
-
-
协议层
-
概念
- 特殊结束符
- 变长协议
-
协议构造
-
LENGTH:数据包大小,不包含自身
-
HEADER MAGIC:标识版本信息,协议解析时会快速校验
-
SEQUENCE NUMBER:表示数据包的seqID,可用于多路复用,单连接内递增
-
HEADER SIZE:头部长度,从第14个字节开始计算到PAYLOAD之前
-
PROTOCOL ID:编解码方式,有Binary、Compact两种
-
TRANSFORM ID:压缩方式,如zlib和snappy
-
INFO ID:传递一些定制的meta信息
-
PAYLOAD:消息体
-
-
协议解析
-
先读取MagicNumber,再读取编解码方式,最后解码消息体
-
-
-
网络通信层
-
Sockets API
-
位于应用层和传输层之间
-
提供易用API
- 封装底层SocketAPI
- 连接管理和事件分发
-
功能
- 支持tcp、udp、uds等
- 优雅退出,异常处理
-
性能
- 应用层buffer减少copy
- 高性能定时器,对象池等
-
-
-
关键指标
稳定性
-
保障策略
- 熔断:保护调用方,防止调用的服务出现问题而影响到整个链路
- 限流:保护被调用方,防止大流量压垮服务
- 超时控制:避免在不可用节点上浪费资源
- 上面三者都可以叫做“降级”
-
请求成功率
- 负载均衡
- 重试
-
长尾请求:明显高于平均响应时间的,占比较小的请求
-
注册中间件
-
可以将上述稳定性策略以注册中间件的方式注入到服务端
-
易用性
- 开箱即用:合理的默认参数选项,丰富的文档
- 周边工具:生成代码工具、脚手架工具
扩展性
-
Middleware
-
Option
-
编解码层
-
协议层
-
网络传输层
-
代码生成工具插件扩展
观测性
-
Log日志服务、Metric监控QPS等、Tracing链式追踪服务状态
-
内置观测性服务
高性能
-
场景
- 单机多机
- 单连接多连接
- 单/多Client 单/多Server
- 不同大小的请求包
- 不同的请求类型:例如pingpong、streaming
-
目标
- 高性能
- 低延迟
-
手段
- 连接池
- 多路复用
- 高性能编解码协议
- 高性能网络库
企业实践
整体架构
remote层是用于和远端交互的层
自研网络库
-
原生库无法感知连接状态
- 池中存在失效连接,影响连接池复用
-
原生库存在Goroutine暴涨的风险
- 一个连接一个Goroutine的模式,由于连接利用率低下,存在大量Goroutine占用调度开销,影响性能。
Netpoll
- 引入epoll监听机制,感知连接状态
- 建立Goroutine池,复用Goroutine
- 引入Nocopy Buffer,向上层提供NoCopy的调用接口,编解码层面零拷贝,提升性能。
扩展性设计
-
支持多协议,可以支持自定义协议扩展
网络库优化
-
调度优化
- epoll_wait在调度上的控制
- gopool重用Goroutine降低同时运行协程数
-
LinkBuffer
- 读写并行无锁,支持nocopy地流式读写
- 高效扩缩容
- Nocopy Buffer池化,减少GC
-
Pool
- 引入内存池和对象池,减少GC开销
编解码优化
-
Codegen
- 预计算并预分配内存,减少内存操作次数,包括内存分配和拷贝
- Inline减少函数调用次数和避免不必要的反射操作
-
JIT(Just In TIme)即时编译
- 基于JIT编译技术的高性能动态Thrift编解码器——Frugal
- 使用JIT编译技术改善用户体验的同时带来更强的编解码功能,减轻用户维护生成代码的负担
合并部署
微服务过微,传输和序列化开销越来越大
将亲和性强的服务实例尽可能调度到同一个物理机,远程RPC调用优化为本地IPC调用
- 中心化部署调度和流量控制
- 基于共享内存的通信协议
- 定制化的服务发现和连接池实现
- 定制化的服务启动和监听逻辑