基本概念
- 远程函数调用(RPC-Remote Procedure Calls)。
- RPC需要解决的问题:
- 函数映射
- 数据转换成字节流
- 网络传输
RPC概念模型
- 1984 Nelson:RPC由5个模型组成:User、User-Stub、RPC-Runtime、Server-Stub、Server
一次RPC的完整过程
- IDL(Interface description language)文件:描述接口,使得不同程序和对象可以相互通信
- 生成代码:把IDL文件转化为语言对应的静态库
- 编解码(序列化和反序列化):从内存中到字节序列的转换称为编码,反之为解码
- 通信协议:规范了数据在网络中的传输内容和格式
- 网络传输:通常基于成熟的网络库走TCP/UDP传输
RPC的好处
- 单一职责,有利于分工协作和运维开发
- 可扩展性强,资源利用率更优
- 故障隔离,服务的整体可靠性更高
RPC的弊端
- 服务宕机
- 调用过程中服务异常,how to 保证消息可达性?
- 请求量突增导致服务无法及时处理 👉通过构建RPC框架解决上述问题
分层设计
编解码层
生成代码
客户端和服务端会依赖同一份IDL文件,生成不同语言的CodeGen(服务)。
数据格式
- 语言特定的格式:许多编程语言都内建了将内存对象编码为字节序列的支持,举个🍘,Java有java.io.Serializable。
- 文本格式:JSON、XML等具有人类可读性的文本格式。
- 二进制编码:具有跨语言和高性能等优点,常见的有Thrift的BinaryProtocol,Protobuf等。
二进制编码
TLV编码:
- Tag:标签,对应类型(方便压缩)
- Length:长度
- Value:值,Value也可以是一个TLV结构
选择的权衡
兼容性(新旧服务兼容)、通用性(跨平台、跨语言)、性能(编码数据大小和耗费时长)
协议层
- 特殊结束符:以一个特殊字符作为每个协议单元结束的标示
- 变长协议:以定长加不定长的部分组成,其中定长的部分需要描述不定长的内容长度
协议构造
LENGTH、HEADER MAGIC、SEQUENCE NUMBER、HEADER SIZE、PROTOCOL ID、TRANSFORM ID、INFO ID、PAYLOAD(消息体)
协议解析
框架会先从内存里读取指定的一部分数据,根据协议的约定读取MagicNumber,从而获取协议类型,再继续读取PayloadCodec,从而获取编解码方式,再通过相应的编解码方式读取Payload,将协议解析,解析后交付上层处理。
网络通信层
Sockets API
- 介于应用层和传输层之间。
- 首先server端获取IP和端口,创建一个套接字,然后执行bind操作,把套接字绑定到对应的地址上,然后再执行listen,监听其他连接(挂到队列里)。如果client端有request请求就是accept接收,然后逐帧read、write,最后执行close操作。
- 客户端只需要启动socket服务,建立连接connection,发送请求request,进行读写read and write,最后执行close操作。
- 两者是同步进行的。
网络库核心指标
- 提供易用API:封装底层Socket API,连接管理和事件分发
- 功能:协议支持TCP、UDP、UDS等,优雅退出、异常处理等。
- 性能:应用层buffer减少copy,高性能定时器、对象池等。
关键指标
稳定性
保障策略
- 熔断:保护调用方,防止被调用的服务出现问题而影响到整个链路
- 限流:保护被调用方,防止大流量把服务压垮
- 超时控制:避免浪费资源在不可用节点上
请求成功率
- 负载均衡:均匀调用节点,合理分配资源
- 重试:服务重试
长尾请求
Backup Request(备份请求):设置超时时间,在Request发出后启动,如果时间结束还没返回响应,就可以重发请求。
注册中间件
通过注册中间件的方式把超时、熔断、重试、限流、负载均衡、Backup Request等的功能加上,灵活地注入各种服务治理策略,保障服务的稳定性。
易用性
- 开箱即用:合理的默认参数选项,丰富的文档
- 周边工具:生成代码工具、脚手架工具(生成服务代码脚手架等)
扩展性
Middleware中间件、Option、编解码层、协议层、网络传输层、代码生成工具插件扩展
观测性
- 传统三件套Log(日志)、Metric(监视)、Tracing(跟踪)
- 内置观测性服务(通过Http服务等了解内部信息框架配置、环境变量、线程、协程等)
高性能
- 目标:高吞吐、低延迟
- 场景:单/多机、单/多连接、单/多client/server、不同大小的请求包、不同请求类型(例如pingpong、streaming等)
- 手段:连接池(提高连接复用率)、多路复用(提高连接复用率)、高性能编解码协议、高性能网络库
企业实践
整体架构——Kitex
- Kitex Core:核心组件(包括client、server等)
- Kitex Byted:与公司内部基础设施集成(包括acl、config等)
- Kitex Tool:代码生成工具(包括cmd、plugin等)
自研网络库
解决 原生库无法感知连接状态(引入epoll主动监听机制)、原生库存在goroutine暴涨的风险(建立goroutine池)、提升性能(引入Nocopy Buffer) 的问题。
扩展性设计
支持多协议,也支持灵活的自定义协议扩展
性能优化
网络库优化
调度优化、LinkBuffer(高效扩缩容)、Pool(引入对象池和内存池,减少GC开销)
编解码优化
- Codegen:预计算并预分配内存,减少内存操作次数,包括内存分配和拷贝;Inline减少函数调用次数和避免不必要的反射操作等;自研了GO语言实现的Thrift IDL解析和代码生成器Thriftgo
- JIT:改善用户体验(即时编译,减轻代码生成负担)、更强的编解码性能、高性能动态Thrift编解码器Frugal
合并部署
- 将亲和性强的服务实例尽可能调度到同一个物理机,远程RPC调用优化为本地IPC调用
- 框架改造:
- 中心化的部署调度和流量控制
- 基于共享内存的通信协议
- 定制化的服务发现和连接池实现
- 定制化的服务启动和监听逻辑