课程目录
- 基本概念
- 分层设计
- 关键指标
- 企业实践
01 基本概念
1.2 远程函数调用
RPC需要解决的问题
- 函数映射
- 数据转换成字节流
- 网络传输
1.4 一次RPC的完整过程
- IDL文件
- 生成代码
- 编解码
- 通信协议
- 网络传输
1.5 RPC的好处
- 单一职责,有利于分工协作和运维开发
- 可扩展性强,资源使用率更优
- 故障隔离,服务的整体可靠性更高
1.6 RPC带来的问题
-
服务宕机,对方应该如何处理?
-
在调用过程中发生网络异常,如何保证消息的可达性?
-
请求量突增导致服务无法及时处理,有哪些应对措施?
由RPC框架处理!
1.7 小结
1.本地函数调用和RPC调用的区别:函数映射、数据转成字节流、网络传输 2.RPC的概念模型:User、User-Stub、RPC-Runtime、Server-Stub、Server 3.一次RPC的完整过程,并讲解了RPC的基本概念定义 4.RPC带来好处的同时也带来了不少新的问题,将由RPC框架来解决
02 分层设计
- 编解码层
- 协议层
- 网络通信层
2.1 分层设计
2.3 编解码层 - 生成代码
RPC框架将同一份IDL文件生成不同语言的代码
2.4 编解码层 - 数据格式
- 语言特定的格式
- 文本格式
- 某语言不支持的某个类型也可以用文本编码
- 二进制编码
- 具备跨语言和高性能特性
2.5 编解码层 - 二进制编码
TLV编码
-
type:数据类型
-
Tag:标签,可以理解为类型
-
Lenght:长度
-
Value:值,Value也可以是个TLV结构
2.6 编解码层 - 选型
- 兼容性
- 支持自动增加新的字段,不影响老的服务,这将提高系统灵活度
- 通用性
- 支持跨平台、跨语言
- 性能
- 空间
- 时间
2.7 协议层
- 把字节流封装成可被传输的数据
2.8 协议层 - 概念
- 特殊结束符
- \r\n
- 变长协议
- 定长 + 不定长
- 定长部分描述不定长的内容长度
2.9 协议层 - 协议构造
-
LENGTH:数据包大小,不包含自身
-
HEADER MAGIC:标识版本信息,协议解析时候快速校验
-
SEQUENCE NUMBER:表示数据包的seqID可用于多路复用,单连接内递增
-
HEADER SIZE:头部长度,从第14个字节开始计算一直到PAYLOAD前
-
PROTOCOL ID:编解码方式,有Binary和
-
Compact两种
-
TRANSFORM ID:压缩方式,如zlib和snappy
-
INFO ID:传递一些定制的meta信息
-
PAYLOAD:消息体
2.10 协议层 - 协议解析
graph LR
a(pre)--peek-->MagicNumber--peek-->PayloadCodec--decode-->Payload
2.12 网络通信层 - Sockets API
2.13 网络通信层 - 网络库
-
现实中会采用 封装好的网络库
-
提供易用AP
-
封装底层Socket API
-
连接管理和事件分发
-
-
功能
-
协议支持:tcp、udp和uds等
-
优雅退出、异常处理等
-
-
性能
-
应用层buffer减少copy
-
高性能定时器、对象池等
-
2.14 小结
-
RPC框架主要核心有三层:编解码层、协议层和网络通信层
-
二进制编解码的实现原理和选型要点
-
协议的一般构造,以及框架协议解析的基本流程
-
网络库的基本架构,以及选型时要考察的核心指标
03 关键指标
- 稳定性
- 易用性
- 扩展性
- 观测性
- 高性能
3.1 稳定性 - 保障策略
-
熔断:保护调用方,防止被调用的服务出现问题而影响到整个链路
-
限流:保护被调用方,防止大流量把服务压垮
-
超时控制:避免浪费资源在不可用节点上
3.2 稳定性 - 请求成功率
- 负载均衡
- 重试
3.3 稳定性 - 长尾请求
-
有约1%的请求会延续很长时间
-
采用 backup request
- 在估计时间内没有返回就立即重发
3.4 稳定性 - 注册中间件
-
可以方便地加上各自功能
-
超时
-
熔断
-
重试
-
限流
-
负载均衡
-
BackupRequest
-
3.5 易用性
-
开箱即用
- 合理的默认参数选项、丰富的文档
-
周边工具
- 生成代码工具、脚手架工具
-
简单易用的命令行工具
-
生成服务代码脚手架
-
支持protobuf和thrift
-
内置功能丰富的选项
-
支持自定义的生成代码插件
-
3.6 扩展性
-
Middleware
-
Option
-
编解码层
-
协议层
-
网络传输层
-
代码生成工具插件扩展
3.7 观测性
- Log、metric、tracing 日志、性能、跟踪
- 内置观测性服务
3.8 高性能
-
场景
-
单机多机
-
单连接多连接
-
单/多client 单/多server
-
不同大小的请求包
-
不同请求类型:例如pingpong、streaming等
-
-
目标
-
高吞吐
-
低延迟
-
-
手段
-
连接池
-
多路复用
-
高性能编解码协议
-
高性能网络库
-
3.9 小结
-
框架通过中间件来注入各种服务治理策略,保障服务的稳定性
-
通过提供合理的默认配置和方便的命令行工具可以提升框架的易用性
-
框架应当提供丰富的扩展点,例如核心的传输层和协议层
-
观测性除了传统的Log、Metric和Tracing之外,内置状态暴露服务也很有必要
-
性能可以从多个层面去优化,例如选择高性能的编解码协议和网络库
04 企业实践
4.1 整体架构 - kitex
-
Kitex Core
- 核心组件
-
Kitex Byted
- 与公司内部基础设施集成
-
Kitex Tool
- 代码生成工具
4.2 自研网络库 - 背景
-
原生库无法感知连接状态
- 在使用连接池时,池中存在失效连接,影响连接池的复用。
-
原生库存在goroutine暴涨的风险
- 一个连接一个goroutine的模式,由于连接利用率低下,存在大量goroutine占用调度开销,影响性能。
4.3 自研网络库 - Netpoll
-
解决无法感知连接状态问题
- 引入epoll主动监听机制,感知连接状态
-
解决goroutine暴涨的风险
- 建立goroutine池,复用goroutine
-
提升性能
- 引入Nocopy Buffer,向上层提供NoCopy的调用接口,编解码层面零拷贝
4.4 扩展性设计
- 支持多协议,也支持灵活的自定义协议扩展
4.5 性能优化
-
调度优化
-
epoll_wait在调度上的控制
-
gopool重用goroutine降低同时运行协程数
-
-
LinkBuffer
-
读写并行无锁,支持nocopy地流式读写
-
高效扩缩容
-
Nocopy Buffer池化,减少GC
-
-
Pool
- 引入内存池和对象池,减少GC开销
4.6 性能优化 - 编解码优化
-
Codegen
-
预计算并预分配内存,减少内存操作次数,包括内存分配和拷贝
-
Inline减少函数调用次数和避免不必要的反射操作等
-
自研了Go语言实现的Thrift IDL解析和代码生成器,支持完善的Thrift IDL语法和语义检查,并支持了插件机制-Thriftgo
-
-
JIT
-
使用JIT编译技术改善用户体验的同时带来更强的编解码性能,减轻用户维护生成代码负担
-
基于JT编译技术的高性能动态Thrift编解码器-Frugal
-
-
参考链接:github/cloudwego
4.7 合并部署
- 微服务过微,传输和序列化开销越来越大
- 将亲和性强的服务实例尽可能调度到同一个物理机,远程RPC调用优化为本地IPC(Inter-Process Communication,进程间通信)调用
中心化的部署调度和流量控制 基于共享内存的通信协议 定制化的服务发现和连接池实现 定制化的服务启动和监听逻辑
4.8 小结
-
介绍了Kitex的整体架构
-
介绍了自研网络库Netpoll的背景和优势
-
从扩展性和性能优化两个方面分享了相关实践
-
介绍了内部正在尝试落地的新的微服务形态:合并部署
课程总结
-
从本地函数调用引出RPC的基本概念
-
重点讲解了RPC框架的核心的三层,编解码层、协议层和网络传输层
-
围绕RPC框架的核心指标,例如稳定性、可扩展性和高性能等,展开讲解相关的知识
-
分享了字节跳动高性能RPC框架Kitex的相关实践