文章目录
- RPC概述
- 一次RPC的完整过程
- RPC的优缺点
- 分层设计
- 编解码层——数据格式
- 协议层——概念
- 网络通信层——网络库
- RPC框架的关键指标
- 稳定性
- 易用性
- 扩展性
- 观测性
- 高性能
- Kitex框架解读
- 整体框架
- 自研网络库——Netpoll
- 扩展性设计
- 性能优化——网络库优化
- 性能优化——编解码优化
- 合并部署
RPC概述
1.本地函数调用和RPC调用的区别:函数映射、数据转成字节流、网络传输
2.RPC的概念模型:User、User-Stub、RPC-Runtime、Server-Stub、Server
3.一次RPC的完整过程
4.RPC带来好处的同时也带来了不少的问题,将由RPC框架来解决
一次RPC的完整过程
IDL(Interface description language)文件:
IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信。
生成代码:
通过编译器工具把IDL文件转换成语言对应的静态库
编解码:
从内存中表示到字节序列的转换称为编码,反之为解码,也常叫做序列化和反序列化
通信协议:
规范了数据在网络中的传输内容和格式。除必须的请求/响应数据外,通常还会包含额外的元数据
网络传输:
通常基于成熟的网络库走TCP/UDP传输
RPC的优缺点
RPC的好处
1.单一职责,有利于分工协作和运维开发
2.可扩展性强,资源使用率更优
3.故障隔离,服务的整体可靠性更高
RPC带来的问题
1.服务宕机,对方应该如何处理?
2.在调用过程中发生网络异常,如何保证消息的可达性?
3.请求量突增导致服务无法及时处理,有哪些应对措施?
分层设计
编解码层——数据格式
语言特点的格式:
许多编程语言都内建了将内存对象编码为字节序列的支持,例如Java有java.io.Serializable
文本格式:
JSON、XML、CSV等文本格式,具有人类可读性
二进制编码:
具备跨语言和高性能等优点,常见有Thrift、BinaryProtocol,Protobuf等
TLV编码:
-Tag:标签,可以理解为类型
-Length:长度
-Value:值,Value也可以是个TLV结构
struct Person{
1:required string userName,
2:optional i64 favoriteNumber,
3:optional list<string> interests
}
编解码——选型
1.兼容性:支持自动增加新的字段,而不影响老的服务,这将提高系统的灵活度
2.通用性:支持跨平台、跨语言
3.性能:从空间和时间两个维度来考虑,也就是编码后数据大小和编码耗费时长
协议层——概念
特殊结束符:
一个特殊字符作为每个协议单元结束的标示
变长协议:
以定长加不定长的部分组成,其中定长的部分需要描述不定长的内容长度
网络通信层——网络库
提供易用API
封装底层Socket API
连接管理和事件分发
功能
协议支持:tcp、udp和uds等
优雅退出、异常处理等
性能
应用层buffer减少copy
高性能定时器、对象池
RPC框架的关键指标
1.框架通过中间件来注入各种服务治理策略,保障服务的稳定性
2.通过提供合理的默认配置和方便的命令行工具可以提升框架的易用性
3.框架应当提供丰富的扩展点,例如核心的传输层和协议层
4.观测性除了传统的Log、Metric和Tracing之外,内置状态暴漏服务也很有必要
5.性能可以从多个层面去优化,例如选择高性能的编解码协议和网络库
稳定性
1.稳定性——保障策略
熔断:保护调用方,防止被调用的服务出现问题而影响到整个链路
限流:保护被调用方,防止大流量把服务压垮
超时控制:避免浪费资源在不可用节点上
2.稳定性——请求成功率
负载均衡:
重试:
3.稳定性——长尾请求
什么是长尾请求?
长尾请求一般是指明显高于均值的那部分占比较小的请求。 业界关于延迟有一个常用的P99标准, 也就是99%的请求延迟要满足在一定耗时以内, 1%的请求会大于这个耗时, 而这1%就可以认为是长尾请求。
长尾请求出现的原因?
共享资源竞争, 周期性的垃圾回收, 运维活动(比如日志备份), 硬件或者软件故障,网络的抖动,都有可能造成。
正常的请求:
长尾请求:超过正常请求应该的响应时间后,发送备用请求
易用性
开箱即用
合理的默认参数选项、丰富的文档
周边工具
生成代码工具、脚手架工具
扩展性
1.Middleware
2.Option
3.编解码层
4.协议层
5.网络传输层
6.代码生成工具插件扩展
观测性
Log、Metric、Tracing
内置观测性服务
高性能
Kitex框架解读
整体框架
Kitex Core:核心组件
Kitex Byted:与公司内部基础设施集成
Kitex Tool:代码生成工具
自研网络库——Netpoll
为什么自研网络库?
原生库无法感知连接状态
在使用连接池时,池中存在失效连接,影响连接池的复用
原生库存在goroutine暴涨的风险
一个连接一个goroutine的模式,由于连接利用率低下,存在大量goroutine占用调度开销,影响性能
Netpoll:
解决无法感知连接状态问题:
引入epoll主动监听机制,感知连接状态
解决goroutine暴涨的风险:
建立goroutine池,复用goroutine
提升性能:
引入Nocopy Buffer,向上层提供NoCopy的调用接口,编解码层面零拷贝
扩展性设计
支持多协议,也支持灵活的自定义协议扩展
性能优化——网络库优化
调度优化
epoll_wait在调度上的控制
gopool重用goroutine降低同时运行协程数
LinkBuffer:
读写并行无锁,支持nocopy地流式读写
高效扩缩容
Nocopy Buffer池化,减少GC
Pool:
引入内存池和对象池,减少GC开销
性能优化——编解码优化
Codegen
预计算并预分配内存,减少内存操作次数,包括内存分配和拷贝
lnline减少函数调用次数和避免不必要的反射操作等
自研了Go语言实现的Thrift IDL解析和代码生成器,支持完善的Thrift IDL语法和语义检查,并支持了插件机制——Thriftgo
JIT
使用JIT编译技术改善用户体验的同时带来更强的编解码性能,减轻用户维护生成代码的负担
基于JIT编译技术的高性能动态Thrift编解码器——Frugal
合并部署
微服务过微,传输和序列化开销越来越大
将亲和性强的服务实例尽可能调度到同一个物理机,远程RPC调用优化为本地IPC调用
中心化的部署调度和流量控制
基于共享内存的通信协议
定制化的服务发现和连接池实现
定制化的服务启动和监听逻辑