这是我参与「第五届青训营 」伴学笔记创作活动的第 12 天
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务
基本概念
本地函数调用
1、将a和b的值压栈
2、通过函数指针找到calculate函数,进入函数取出栈中的值2和3,将其赋予x和y
3、计算x*y,并将结果存在z
4、将z的值压栈,然后从calculate返回
5、从栈中取出z返回值,并赋值给result
func main() {
var a=2
var b=3
result := calculate(a,b)
fmt.Println(result)
return
}
func calculate(x,y int) {
z := x*y
return z
}
远程函数调用(RPC - Remote Procedure Calls)
RPC需要解决的问题
- 函数映射
- 数据转换成字节流
- 网络传输
网上商城-----------支付100元----------->支付服务--------余额减掉100元--------->返回扣款成功--------->网上商城
RPC概念模型
1984年Neison发表了论文《Implementing Remote Procedure Calls》,其中提出了RPC的过程由5个模型组成:Users、User-Stub、RPC-Runtime、Server-Stub、Server
一次完整的RPC过程
IDL(Interface description language)文件
IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和不同语言编写的程序可以相互通信。
生成代码
通过编译器工具把IDL文件转换成语言对应的静态库
编解码
从内存中表示到字节序列的转换成为编码,反之为解码,也常常叫做序列化和反序列化
通信协议
规范了数据在网络中的传输内容和格式,除必须的请求/响应数据外,通常还会包含额外的元数据
RPC的优点
- 单一职责,有利于分工协作和运维开发
- 可扩展性强,资源使用率更优
- 故障隔离,服务的整体可靠性更高
RPC的缺点
- 服务宕机,对方应该如何处理
- 在调用过程中发生网络异常,如何保证消息的可达性
- 请求量突然增加导致服务无法及时处理,有哪些应对措施
分层设计
分层设计 - 以Apache Thrift为例
编解码层 - 生成代码
编解码层 - 数据格式
- 语言特定的格式
- 文本格式
- 二进制编码
编解码层 - 二进制编码
TLV编码
- Tag:标签,可以理解为类型
- Length:长度
- Value:值,这个值也可以十个TLV结构
编解码层 - 选型
- 兼容性 支持自动增加新的字段,而不影响老的服务,这将提高系统的灵活度
- 通用性 支持跨平台,跨语言
- 性能 从空间和时间两个维度来考虑,也就是编码后数据大小和编码耗费时长。
协议层 - 概念
- 特殊结束符 一个特殊字符作为每个协议单元结束的标示
- 变长协议 以定长加不定长的部分组成,其中定长的部分需要描述不定长的内容长度
协议层 - 协议构造
协议层 - 协议解析
网络通信层 - Sockets API
网络通信层 - 网络库
- 提供常用API
- 封装底层Socket API
- 连接管理和事件分发
- 功能
- 协议支持:tcp、udp和uds等
- 优雅推出、异常处理等
- 性能
- 应用层buffer减少copy
- 高性能定时器、对象池等
关键指标
稳定性 - 保障策略
- 熔断
- 限流
- 超时控制
稳定性 - 请求成功率
负载均衡、重试
稳定性 - 长尾请求
稳定性 - 注册中间件
易用性
- 开箱即用 合理的默认参数选项、丰富的文档
- 周边工具 生成代码工具、脚手架工具
扩展性
- Middleware
- Option
- 编解码层
- 协议层
- 网络传输层
- 代码生成工具插件扩展
观测性
- Log、Metric、Tracing
- 内置观测性服务
高性能
企业实践
整体架构 - Kitex
- Kitex Core 核心组件
- Kitex Byted 与公司内部基础设施集成
- Kitex Tool 代码生成工具
自研网络库
背景
- 原生库无法感知连接状态
- 原生库存在goroutine暴涨的风险
Netpoll
- 解决无法感知连接状态问题
- 解决goroutine暴涨的风险
- 提升性能
扩展性设计
性能优化
网络库优化
- 调度优化
- LinkeBuffer
- Pool
编解码优化
- Codegen
- JIT
合并部署
微服务过微,传输和序列化开销越来越大。将亲和性强的服务实例尽可能调度到同一个物理机,远程RPC调用优化为本地RPC调用。