[toc]
基本概念
see:RPC
RPC分层设计
例子:Apache Thrift

编解码层
生成代码
IDL文件描述了接口的规范,这样即使不同语言编写的程序都可以使用。

数据格式
- 语言特定格式:编程语言将内存数据编码成字节序列,其他语言无法读取
- 文本格式:具有人类可读性的格式,性能比较差,如Json、XML、YAML
- 二进制编码:把数据转换成二进制流跨语言、高性能,常见的有
Thriift的BinarProtocol等
TLV编码:
- Tag:类型
- Length:长度
- Value:值,Value也可以是一个TLV
- tag和length占用了额外的空间

协议层
概念
特殊结束符:过于简单,对于一个协议单元必须要全部读入才能够进行处理,除此之外必须要防止用户传输的数据不能同结束符相同,否则就会出现紊乱。
HTTP 协议头就是以回车(CR)加换行(LF)符号序列结尾。

变长协议:一般都是自定义协议,有 header 和 payload 组成,会以定长加不定长的部分组成,其中定长的部分需要描述不定长的内容长度,使用比较广泛

协议构造

协议解析

网络通信层
Sockets API
位于传输层和应用层之间

网络库
- 对上层提供API
- 封装底层Socket API
- 连接管理和事件分发
- 协议支持
- TCP
- UDP
- UDS
- 优雅退出
- 异常处理
- 性能
- 应用层buffer减少copy
- 高性能定时器、对象池
小结
- RPC三个核心层
- 二进制编解码原理、选型
- 协议构造解析流程
- socket api调用流程
- 网络库指标
RPC关键指标
稳定性
保障策略

熔断:保护调用方,防止调用服务出现问题而影响到整个链路
A调用B,B调用C。如果C超时了,那么B也会超时,那么A就会频繁的调用B,B会因为堆积大量请求导致服务宕机
限流:限制流量,防止大流量把线路压垮了
超时控制:被调用端响应过慢,调用端会主动停止不重要的请求,即使释放资源。
请求成功率
- 负载均衡:避免单个服务的负载过大
- 重试:调用失败会重试几次,超过次数才算真正的失败

长尾请求
长尾请求:响应时间明显高于平均响应时间的请求
pk99:按请求长度从小到大排列,超过99%的长度的会被认为长尾请求
处理长尾请求:在返回之前重新发送一次。按照过往经验来看99%的请求能在t3内返回,这时发送备份请求Req2,Req2会很快返回。

注册中间件(拦截器)
在创建时将这些功能可选地加上

易用性
- 开箱即用:合理的默认配置、丰富的文档
- 周边工具:生成代码、脚手架
扩展性
client发送消息会依次通过中间件处理,server亦然。

观测性
观测手段
- Log、Metric、Tracing
- 内置观测服务:Linux中的
top命令

性能
场景:
- 单机、多机
- 单连接、多连接
- 单/多 client/server
- 请求大小
- 请求类型:pingpong,streaming
目标:
- 高吞吐
- 低延迟
手段:
- 连接池
- 多路复用
- 高性能编解码协议
- 高性能网络库
企业实践——kitex
整体架构
kitex core:核心组件kitex byted:字节内部设施基础kitex tool:代码生成工具

自研网络库
原生网络库缺点:
- 无法感知连接状态:使用连接池时,池中存在失效连接,影响连接池复用
- 存在goroutine暴涨风险:一个连接一个goroutine,利用率低下
Netpoll
- 解决无法感知连接状态的问题:使用epoll主动监听
- 解决goroutine暴涨风险:建立goroutine池,复用goroutine
- 提升性能:引入
Nocopy Buffer,向上层提供Nocopy调用接口,减少拷贝
扩展性设计

性能优化
调度优化
- 优化
epoll_wait调度上的控制 - gopoll重用goroutine,降低同时运行的协程数量
LinkBuffer
- 读写并行无锁,支持nocopy流式读写
- 高效扩容缩容
- Nocopy Buffer池化,减少GC
Poll
引入对象池和内存池,减少GC开销
合并部署
参考
深入浅出 RPC 框架 副本.pptx - 飞书云文档 (feishu.cn)