14 RPC 原理与实践 | 青训营笔记

131 阅读7分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第15天

资料: juejin.cn/post/719632…

课程1: juejin.cn/course/byte…

课程2: juejin.cn/course/byte…

PPT: bytedance.feishu.cn/file/boxcn5…

讲师: 江学武

Kitex: www.cloudwego.io/zh/docs/kit…

Netpoll: www.cloudwego.io/zh/docs/net…

01 基本概念

 RPC: Remote Procedure Calls
 需要解决的问题:
     1 函数映射
     2 数据转换成字节流
     3 网络传输
 优点:
     1 单一职责, 有利于分工协作和运维开发
     2 可扩展性强, 资源使用率更优
     3 故障隔离, 服务的整体可靠性更高
 问题:
     1 服务宕机, 对方应该如何处理?
     2 调用过程网络异常, 如何保证消息的可达性?
     3 请求量突增导致服务无法即时处理, 如何解决?

一次RPC的完成过程

过程见PPT图

相关概念如下

 IDL(Interface description Language)文件
     IDL通过一种中立的方式来描述接口, 使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信
 生产代码
     通过编译器工具把IDL文件转换成语言对应的静态库
 编解码
     从内存中表示到字节序列的转换称为编码, 反之为解码, 也常叫做序列化和反序列化
 通信协议
     规范了数据在网络中传输内容和格式. 除必要的请求/响应数据外, 通常还会包含额外的元数据
 网络传输
     通常基于成熟的网络库走TCP/UDP传输

小结

 1 本地函数调用和RPC调用的区别: 函数映射, 数据转换成字节流, 网络传输
 2 RPC的概念模型: User, User-Stub, RPC-Runtime, Server-Stub, Server
 3 一次RPC的完成过程, 相关基本概念
 4 RPC的好处, 和问题(问题将由RPC框架解决)

02 分层设计

1 编解码层

数据格式

 语言特定格式
    许多编程语言内建了将内存对象编码为字节序列的支持, 例如Java有java.io.Serializable
 文本格式
     JSON, XML, CSV等文本格式, 具有人类可读性
 二进制格式
     具有跨语言和高性能等优点, 如ThriftBinaryProtocol, Protobuf

二进制编码

 TLV编码:
     Tag: 标签, 可以理解为类型
     Lenght: 长度
     Value: 值, Value也可以是个TLV结构

选型

 兼容性
     支持自动增加新的字段, 而不影响老的服务, 提高系统的灵活性
 通用性
     支持跨平台, 跨语言
 性能
     从空间和时间两个维度来考虑, 也就是编码后数据大小, 和编码耗费时长

2 协议层

概念

 特殊结束符: 一个特殊字符作为每个协议单元结束的标示
 变长协议: 以定长加不定长的部分组成, 其中定长的部分需要描述不定长的内容长度

协议构造

 LENGTH: 数据包大小, 不包含自身
 HEADER MAGIC: 表示版本信息, 协议解析时候快速校验
 SEQUENCE NUMBER: 表示数据包的seqlID, 可用于多路复用, 单连接内递增
 HEADER SIZE: 头部长度, 从第14个字节开始计算到PAYLOAD前
 PROTOCOL ID: 编解码方式, 有Binary和Compact两种
 TRANSFORM ID: 压缩方式, 如zlib和snappy
 INFO ID: 传递一些定制的meta信息
 PAYLOAD: 消息体

协议解析

 ↓Peek
 MagicNumber
 ↓Peek
 PayloadCodec(Codec: 编码译码器)
 Decode
 Payload

3 网络通信层

Sockets API

网络库

 提供易用API
     封装底层Socket API
     连接管理和事件分发
 功能
     协议支持: tcp, udp 和 uds等
     优雅退出, 异常处理等
 性能
     应用层buffer减少copy
     高性能定时器, 对象池等

小结

 1 RPC框架主要核心有三层: 编解码层, 协议层和网络通信层
 2 二进制编解码的实现原理和选型要点
 3 协议的一般构造, 以及框架协议解析的基本流程
 4 网络库的基本架构, 以及选型时要考察的核心指标

03 关键指标

⭐学会鉴赏框架

稳定性

 熔断: 保护调用方, 防止被调用的服务出现问题而影响整个链路
 限流: 保护被调用方, 防止大流量把服务压垮
 超时控制: 避免浪费资源在不可以节点上
 请求成功率
 提高方法:
     负载均衡
     重试
 长尾请求
 优化方法:
     backup request

可以通过注册中间件补充上述功能

易用性

 开箱即用
     合理的默认参数选型, 丰富的文档
 周边工具
     生产代码工具, 脚手架工具

扩展性

提供尽可能多的扩展点

Middleware(中间件)
Option
编解码层
协议层
网络传输层
代码生成工具插件扩展

观测性

 Log, Metric, Tracing
内置观测性服务\

高性能

场景:
单机/多级
单连接/多连接
单/多client 单/多server
不同大小的请求包
不同请求类型: 例如pingpong, streaming
目标:
高吞吐
低延迟
手段:
连接池
多路复用
高性能编解码协议
高性能网络库

小结

1 框架通过中间件来注入各种服务治理策略, 保障服务的稳定性
2 通过提供合理的默认配置和方便的命令行工具可以提升框架的易用性
3 框架应当提供丰富的扩展点, 例如核心的传输层和协议层
4 观测性除了传统的Log, Metric, 和Tracing之外, 内置状态暴露服务也很有必要
5 性能可以从多个层面去优化, 例如选择高性能的编解码协议和网络库

04 企业实践

整体架构 - Kitex

Kitex Core 核心组件
Kitex Byted 与公司内部基础设施集成
Kitex Tool 代码生成工具

自研网络库

背景

原生库无法感知连接状态
在使用连接池时, 池中存在失效连接, 影响连接池的复用
原生库存在goroutine暴涨的风险
一个连接一个goroutine的模式, 由于连接利用率低下, 存在大量goroutine占用调度开销, 影响性能

Netpoll

解决无法感知连接状态问题
引入epoll主动监听机制, 感知连接状态
解决goroutine暴涨的风险
建立goroutine池, 复用goroutine
提升性能
引入Nocopy Buffer, 向上层提供NoCopy的调用接口, 编解码层面零拷贝

扩展性设计

 支持多协议, 也支持灵活的自定义协议扩展
Interaction: Ping-Pong/Streaming/Oneway
Codec: Thrift/Protobuf
Application Layer Protocol: TTHeader/HTTP2/-
Transport Layer: TCP/UDP/RDMA

性能优化

网络库优化

调度优化
epoll_wait在调度上的控制
gopool重用goroutine降低同时运行协程数
LinkBuffer
读写并行无锁, 支持nocopy地流式读写
高效扩缩容
Nocopy Buffer池化, 减少GC
Pool
引入内存池和对象池, 减少GC开销

编解码优化

Codegen
预计算并预分配内存, 减少内存分配操作次数, 包括内存分配和拷贝
Inline减少函数调用次数和避免不必要的反射操作等
自研了Go语言实现的Thrift IDL解析和代码生成器, 支持完善的Thrift IDL语法和语义检查, 并支持了插件机制 - Thriftgo
JIT
使用JIT编译技术改善用户体验的同时带来更强的编解码性能, 减轻用户维护生成代码的负担, 基于JIT编译技术的高性能动态Thrift编解码器 - Frugal

合并部署 (同07课 架构初探中的亲和部署)

 微服务过微, 传输和序列化开销越来越大
 将亲和性强的服务实例尽可能调度到同一个物理机, 远程RPC调用优化为本地IPC调用

小结

1 介绍了Kitex的整体架构
2 介绍了自研网络库Netpoll的背景和优势
3 从扩展性和性能优化两个方面分享了相关实践
4 介绍了内部正在尝试落地的新的微服务形态: 合并部署

课程总结

 1 从本地函数调用引出RPC的基本概念
 2 充电讲解了RPC框架的核心的三层: 编解码层, 协议层, 和网络传输层
 3 围绕RPC框架的核心指标, 例如稳定性, 可扩展性, 和高性能等, 展开讲解相关的知识
 4 分享了字节跳动高性能RPC框架Kitex的相关实践