基本概念
- 本地函数调用(举例说明)
- 远程函数调用(RPC——Remote Procedure Calls)
- 比如一个远程支付操作
- RPC需要解决的问题(和本地调用的区别)
- 函数映射
- 数据转换成字节流
- 网络传输
- RPC概念模型
- 一次RPC完整流程
相关概念
- IDL文件——IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信
- 生成代码——通过编译器工具把IDL文件转换成语言对应的静态库
- 编解码——从内存中表示到字节序列的转换成为编码,反之为解码,也常叫做序列化和反序列化
- 通信协议——规范了数据在网络中的传输内容和格式。除必须的请求/响应数据外,通常还会包含额外的元数据
- 网络传输——通常基于成熟的网络库走TCP/UDP传输
- RPC的好处
- 单一职责,有利于分工协作和运维开发
- 可扩展性强,资源使用率更优
- 故障隔离,服务的整体可靠性更高
- RPC带来的问题——>RPC框架
分层设计(RPC框架)
分层模型——以Apache Thrift为例
一个RPC通信需要经过:生成代码、编解码层、协议层、网络传输层
其中属于框架的部分有三层:编解码层、协议层、网络传输层
编解码层
- 生成代码(编解码的前提)
- 数据格式(包括以下三方面)
- 语言特定格式:许多编程语言都内建了将内存对象编码为字节序列的支持,例如Java有java.io.Serializable
- 文本格式:JSON、XML、CSV等文本格式,对人来说具有可读性
- 二进制编码:具备跨语言和高性能等优点,常见有Thrift的BinaryProtocol,Protobuf等(下面将举例展开讲解其实现原理和选型要点)
- 二进制编码(以TLV编码为例)
- Tag:标签/类型
- Length:长度
- Value:值(可以是TLV结构)
- 二进制编码选型(考虑以下几个方面)
- 兼容性(支持自动增加新的字段,而不影响老的服务,将提高系统灵活度)
- 通用性(支持跨平台、跨语言)
- 性能(从时间和空间两个维度来考虑,也就是编码后数据大小和编码耗费时长)
- 协议层
- 部分结构
- 特殊结束符(\r\n)——一个特殊字符作为每个协议单元结束的标示
- 部分结构
- 变长协议——以定长+不定长的部分组成,其中定长的部分需要描述不定长部分的内容长度
- 协议构造
- 协议解析
流程:
- 网络通信层
- Sockets API
- Sockets API 连接应用层和传输层
- Sockets API
2. 一次连接所需的Sockets API大致如下
2. 网络库选型指标 - 提供易用API - 封装底层Socket API - 连接管理和事件分发 - 功能 - 协议支持:tcp、udp、uds等 - 优雅退出、异常处理等 - 性能 - 应用层buffer减少copy - 高性能定时器、对象池
关键指标
稳定性
- 保障策略
- 熔断:保护调用方,防止被调用的服务出现问题而影响整个链路
- 限流:保护被调用方,防止大流量把服务压垮
- 超时控制:避免浪费资源在不可用节点上
- 请求成功率
- 负载均衡(提高成功率)
- 重试(请求失败重试,因为可能服务处理较慢并非异常)
- 长尾请求(设定PCT99——99%的情况下一次请求响应的时间)
- 正常情况下 b. 存在长尾请求的情况下(保证稳定性,t3=PCT99)
- 注册中间件(将以上提到的保证稳定性的策略都集合到中间件里,并注册中间件)
易用性
- 开箱即用
- 框架有较为丰富的文档,明了易懂
- 默认参数选项合理,如下所示,提供了合理的功能
扩展性
观测性
- Log(日志记录)、Metric(监视)、Tracing(链路跟踪)
- 内置观测性服务
高性能
- 分场景
- 单机多机
- 单机多连接
- 单/多client 单/多server
- 不同大小的请求包
- 不同请求类型:例如pingpong、streaming等
- 目标
- 高吞吐
- 低延迟
- 手段
企业实践
整体架构——kitex
自研网络库
- 背景
- 原生库无法感知连接状态(在使用连接池时,池中存在失效连接,影响连接池的复用)
- 原生库存在goroutine暴涨的风险(一个连接一个goroutine,连接利用率低,大量goroutine占用调度开销,影响性能)
- Netpoll
实现的功能
- 解决无法感知连接状态问题(引入epoll主动监听机制,感知连接状态)
- 解决goroutine暴涨的风险(建立goroutine池,复用goroutine)
- 提升性能(引入Nocopy Buffer,向上层提供NoCopy的调用接口,编解码层面零拷贝)
扩展性设计
性能优化
- 网络库优化
- 调度优化
- epoll_wait在调度上的控制
- gopool 重用goroutine降低同时运行协程数
- LinkBuffer
- 读写并行无锁,支持nocopy地流式读写
- 高效扩缩容
- Nocopy Buffer池化,减少GC
- Pool
- 引入内存池和对象池,减少GC开销
- 调度优化
- 编解码优化
合并部署
- 问题:微服务过微,传输和序列化开销越来越大
- 解决:将亲和性强的服务实例尽可能调度到同一个物理机,远程RPC调用优化为本地IPC调用
- 合并部署内部设定
- 中心化的部署调度和流量控制
- 基于共享内存的通信协议
- 定制化的服务发现和连接池实现
- 定制化的服务启动和监听逻辑
- 落地效果
抖音某服务,30%合并流量,服务端CPU减少19%,延迟PCT99减少29%