这是我参与「第五届青训营 」笔记创作活动的第15天。注:笔记大部分图片内容及代码段为青训营课程视频提供,仅交流,不得做个人使用
一、本课主要内容
- RPC
二、本节详细知识点
RPC
--基本概念
--分层设计
编解码层、协议层、网络通信层
--关键指标
稳定性、易用性、扩展性、观测性、高性能
--企业实践
整体架构、自研网络库、扩展性设计、性能优化、合并部署
1RPC基本概念
1.1本地函数调用
1.将a和b的值压栈
2.通过函数指针找到calculate函数,进入函数取出栈中的值2和3,将其赋予×和y
3.计算×*y,并将结果存在z
4.将z的值压栈,然后从calculate返回
5.从栈中取出z返回值,并赋值给result
1.2远程函数调用(RPC- Remote Procedure Calls)
RPC需要解决的问题1.函数映射2.数据转换成字节流3.网络传输
1.3RPC概念模型
1984年Nelson发表了论文《lmplementing Remote Procedure Calls)其中提出了 RPC的过程由5个模型组成 User、 User-Stub、 RPC-Runtime、 Server-Stub、 Server
1.4一次RPC的完整过程
IDL(lnterface description language)文件IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信
生成代码通过编译器工具把IDL文件转换成语言对应的静态库
编解码从内存中表示到字节序列的转换称为编码,反之为解码,也常叫做序列化和反序列化
通信协议规范了数据在网络中的传输内容和格式。除必须的请求/响应数据外,通常还会包含额外的元数据
网络传输通常基于成熟的网络库走TCP/UDP传输
1.5RPC的好处
1.单一职责,有利于分工协作和运维开发
2.可扩展性强,资源使用率更优
3.故障隔离,服务的整体可靠性更高
1.6RPC的弊端
1.服务岩机,对方应该如何处理?
2.在调用过程中发生网络异常,如何保证消息的可达性?
3.请求量突增导致服务无法及时处理,有哪些应对措施?
于是我们需要学习RPC框架
1.7小结
1.本地函数调用和RPC调用的区别:函数映射、数据转成字节流、网络传输
2.RPC的概念模型:User、User-Stub、RPC-Runtime、Server-Stub、Senver
3.一次PRC的完整过程,并讲解了RPC的基本概念定义
4.RPC带来好处的同时也带来了不少新的问题,将由RPC框架来解决
2分层设计
2.1分层设计 Apache Thrift 为例
2.2编解码层
2.3编解码层-生成代码
2.4编解码层-数据格式
- 语言特定的格式许多编程语言都内建了将内存对象编码为字节序列的支持,例如Java有java.io.Serializable(缺陷是只能绑定本语言,无兼容性)
- 文本格式JSON、XML、CSV等文本格式,具有人类可读性(描述不严谨,例如json不能描述整数浮点数;无约束,调试不方便;常用仿射机制,性能差)
- 二进制编码具备跨语言和高性能等优点,常见有Thrift的BinaryProtocol,Protobuf等
2.5编解码层-二进制编码
TLV 编码
-Tag:标签,可以理解为类型
-Lenght:长度
-Value:值,Value也可以是个TLV结构
2.6编解码层-选型
- 当我们需要选择编码格式的时候,应该考量哪些问题?
兼容性:支持自动增加新的字段,而不影响老的服务,这将提高系统的灵活度
通用性:支持跨平台、跨语言(、流行性)
性能:从空间和时间两个维度来考虑,也就是编码后数据大小和编码耗费时长
2.7协议层
2.8协议层-概念
- 特殊结束符
- 变长协议
2.8协议层-协议构造
LENGTH: 数据包大小,不包含自身
HEADER MAGIC: 标识版本信息,协议解析时候快速校验
SEQUENCE NUMBER:表示数据包的seqID,可用于多路复用,单连接内递增
HEADER SIZE:头部长度,从第14个字节开始计算一直到PAYLOAD前
PROTOCOL ID:编解码方式,有Binary和Compact两种
TRANSFORM ID:压缩方式,如zlib和snappy
INFOID:传递一些定制的meta信息
PAYLOAD: 消息体
2.10协议层-协议解析
读取魔法数,知道是什么类型的协议。->读取编解码方式->解读payload
2.11网络通信层
2.12网络通信层-Sockets APs
应用层和传输层
2.13网络通信层-网络库
现实中常使用封装好的网络库作为RPC框架的网络通信层
- 提供易用API
封装底层SocketAPI
连接管理和事件分发 - 功能
协议支持:tcp、udp和uds等
优雅退出、异常处理等 - 性能
应用层buffer减少copy
高性能定时器、对象池等
2.14 小结
1.RPC框架主要核心有三层:编解码层、协议层和网络通信层
2.二进制编解码的实现原理和选型要点
3.协议的一般构造,以及框架协议解析的基本流程
4.网络库的基本架构,以及选型时要考察的核心指标
3关键指标
3.1 稳定性-保障策略
熔断:保护调用方,防止被调用的服务出现问题而影响到整个链路
限流:保护被调用方,防止大流量把服务压跨
超时控制:避免浪费资源在不可用节点上
3.2 稳定性-请求成功率
负载均衡:
重试:
3.3 稳定性-长尾请求
Backup Request(备份请求)
达到t3时限便Req2
3.4稳定性-注册中间件
client或server创建时可选
3.5易用性
- 开箱即用
合理的默认参数选项、丰富的文档 - 周边工具
生成代码工具、脚手架工具
3.6 扩展性
Middleware
Option
编解码层
协议层
网络传输层
代码生成工具插件扩展
有序调用链(中间件)
3.7观测性
外置方盒以及内置服务
3.8高性能
- 场景
单机多机
单连接、多连接
单/多client、单/多server
不同大小的请求包
不同请求类型:例如pingpong、streaming等 - 目标
高吞吐、低延迟 - 手段
连接池、多路复用、高性能编解码协议、高性能网络库
3.9小结
1.框架通过中间件来注入各种服务治理策略,保障服务的稳定性
2.通过提供合理的默认配置和方便的命令行工具可以提升框架的易用性
3.框架应当提供丰富的扩展点,例如核心的传输层和协议层
4.观测性除了传统的Log、Metric和Tracing之外,内置状态暴露服务也很有必要
5.性能可以从多个层面去优化,例如选择高性能的编解码协议和网络库
4企业实践(字节)
4.1整体架构-Kitex
Kitex Core 核心组件、Kitex Byted 与公司内部基础设施集成、Kitex Tool 代码生成工具
4.2自研网络库-背景
Go原生库无法感知连接状态:在使用连接池时,池中存在失效连接,影响连接池的复用
Go原生库存在goroutine 暴涨的风险:一个连接一个goroutine的模式,由于连接利用率低下,存在大量goroutine占用调度开销,影响性能。
4.3自研网络库-Netpoll
解决无法感知连接状态问题:引入epoll主动监听机制,感知连接状态
解决goroutine暴涨的风险:建立goroutine池,复用goroutine
提升性能:引入NocopyBuffer,向上层提供NoCopy的调用接口,编解码层面零拷贝
4.4Kitex扩展性
支持多协议,也支持灵活的自定义协议拓展
4.5Kitex性能优化-网络库优化
调度优化:epollwait在调度上的控制、gopool重用goroutine降低同时运行协程数
LinkBuffer: 读写并行无锁,支持nocopy地流式读写、高效扩缩容、NocopyBuffer池化,减少GC
Pool: 引入内存池和对象池,减少GC开销
4.65Kitex性能优化-编解码优化
Codegen
预计算并预分配内存,减少内存操作次数,包括内存分配和拷贝
Inline减少函数调用次数和避免不必要的反射操作等
自研了Go语言实现的ThriftIDL解析和代码生成器,支持完善的ThriftIDL语法和语义检查,并支持了插件机制-Thriftgo
JIT (just in time)即时编译
使用JIT编译技术改善用户体验的同时带来更强的编解码性能,减轻用户维护生成代码的负担
基于JIT编译技术的高性能动态Thrift编解码器-Frugal
4.7合并部署(一个新的微服务部署形态)
微服务过微,传输和序列化开销越来越大
将亲和性强的服务实例尽可能调度到同司一个物理机,远程RPC调用优化为本地IPC调用
进行定制化的改造来解决问题
中心化的部署调度和流量控制
基于共享内存的通信协议
定制化的服务发现和连接池实现
定制化的服务启动和监听逻辑
字节内部库(右)与开源库(左)的改造依赖关系
优化成果:
4.8小结
介绍了Kitex的整体架构
介绍了自研网络库Netpoll的背景和优势
从扩展性和性能优化两个方面分享了相关实践
介绍了内部正在尝试落地的新的微服务形态:合并部署
本节总结
1、从本地函数调用引出RPC的基本概念
2、重点讲解了RPC框架的核心的三层,编解码层、协议层和网络传输层
3、围绕RPC框架的核心指标,例如稳定性、可扩展性和高性能等,展开讲解相关的知识
4、分享字节跳动高性能RPC框架Kitex的相关实践