这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天
RPC框架
基本概念
本地函数调用与远程函数调用
-
本地函数调用
参数压栈->call->执行目标函数->返回
-
远程函数调用
需要解决如下一些问题:
-
如何找到目标函数?
函数映射
-
如何传输参数?
数据序列化
-
网络传输
-
RPC概念模型
1984年Nelson发表的论文中,提出了RPC过程由五个模型组成:
- User
- User-Stub
- RPC-Runtime
- Server-Stub
- Server
实际RPC的一些概念
- IDL(接口定义语言)
- 生成代码:通过编译器工具把IDL转换为对应语言的静态库
- 编解码:内存数据到字节序列的转换
- 通信协议:除了请求、响应之外,还包含网络传输的元数据
- 网络传输:通常基于网络库并通过TCP/UDP传输
RPC的优点有哪些?
- 单一职责:不同的服务之间相互独立,可以采用不同的技术栈开发并独立开发和运维
- 可扩展性强:可以扩展需要的服务,而保持其他服务不变
- 故障隔离:服务整体的可靠性更高
RPC带来的问题有哪些?
- 被调用方宕机如何处理?
- 调用过程中发生网络异常,如何保证消息可达?
- 请求量剧增导致无法及时处理时,如何应对?
RPC框架需要解决使用RPC带来的这些问题。
分层设计
Apache Thrift框架的整体架构如下所示
编解码层
-
代码生成
通过IDL文件,生成统一的跨语言的数据格式。
数据编码格式可以大致分为三类:
- 语言内置的序列化例如java的serialize
- 人类可读的文本编码例如JSON、XML等
- 二进制编码例如protobuf
-
编码格式的选择
一般考虑如下一些特性
- 兼容性(自动增加新字段、而不影响老服务)
- 通用性:跨平台、跨语言
- 性能:编解码的时间空间开销
协议层
-
协议层的分类与概念
- 特殊结束符:采用特殊结束符作为每个协议单元的结束(例如
msg\r\nmsg)
- 变长协议:以定长(协议头)+不定长的部分组成,定长部分需要描述长度
- 特殊结束符:采用特殊结束符作为每个协议单元的结束(例如
-
协议的构造
- HEADER + PAYLOAD
-
协议解析
- MagicNumber -> PayloadCodec -> Decode Payload
网络通信层
-
Socket API
直接使用操作系统提供的API,比较麻烦
-
使用网络库实现
- 提供易用的API,包含连接管理和事件分发
- 功能:支持tcp、udp和uds等,并提供优雅退出和异常处理
- 性能:采用buffer减少复制,通过对象池、高性能定时器等提高性能
关键指标
稳定性
-
保障策略:
- 熔断:保护调用方,防止被调用的服务出现问题而影响整个链路
- 限流:保护被调用方,防止大流量压垮服务
- 超时控制:防止资源浪费在不可用的节点上
-
请求成功率:
- 负载均衡
- 重试
-
长尾请求(pct99)
- Backup Request(提前重试)
框架如何实现以上策略呢?通过在框架中注册Middle Ware来实现。
易用性
- 开箱即用:提供合理的默认配置以及丰富的文档
- 周边工具:代码生成器、脚手架工具
扩展性
-
支持MiddleWare
-
支持多种选项
-
支持多种
- 编解码格式
- 协议
- 网络传输
-
代码生成工具插件扩展
观测性
- Log:日志
- Metric:监控面板
- Tracing:链路追踪
在RPC框架中内置观测性服务(例如日志平台、框架配置查看、环境变量等等)
高性能
-
适配不同场景
-
性能目标:
- 高吞吐
- 低延迟
-
优化手段
- 连接池
- 多路复用
- 高性能编解码
- 高性能网络库