【Go 语言框架与实现 学习资料】第三届字节跳动青训营 - 后端专场
theme: juejin
第三届字节跳动青训营讲师非常用心给大家整理了课前、中、后的学习内容,同学们自我评估,选择性查漏补缺,便于大家更好的跟上讲师们的节奏,祝大家学习愉快,多多提问交流~
第十三节:深入浅出 RPC 框架
概述
本节课程主要分为四个方面:
- RPC 相关的基本概念
- RPC 框架的分层设计
- 衡量 RPC 框架的一些核心指标
- 字节内部 RPC 框架 Kitex 实践分享
课前部分主要罗列课程中涉及到的概念。对于不熟悉的概念,同学们可以提前查询预习;
课中部分主要罗列每一部分的关键思路,帮助同学们跟上课程的进度;
课后部分是一些问题,帮助同学们在课后梳理本课程的重点。
课前
RPC 的基本概念
-
RPC的概念模型:User、User-Stub、RPC-Runtime、Server-Stub、Server
-
IDL(Interface Definition Language) 文件
- Thrift
- Protobuf
- 生成代码
- 编解码(序列化/反序列化)
-
通信协议
- 应用层协议
-
网络通信
-
IO 网络模型
- blocking IO
- unblocking IO
- IO multiplexing
- signal driven IO
- asynchronous IO
-
传输层协议
- TCP
- UDP
-
RPC 框架分层设计
-
编解码层
-
数据格式:
-
语言特定格式
-
文本格式
-
二进制编码
- TLV 编码:Thrift 使用 TLV 编码
- Varint 编码:Protobuf 使用 Varint 编码
-
-
选项:
- 兼容性
- 通用型
- 性能
-
-
传输协议层
-
消息切分
- 特殊结束符
- 变长协议:length+body
-
协议构造
- 以 Thrift 的 THeader 协议为例讲解
-
-
网络通信层
-
网络库
-
核心指标
- 吞吐高
- 延迟低
-
RPC 框架的核心指标
-
稳定性
-
保障策略
- 熔断
- 限流
- 超时
-
请求成功率
- 负载均衡
- 重试
-
长尾请求
- BackupRequest
-
-
易用性
- 开箱即用
- 周边工具
- 扩展性
-
观测性
- Log
- Metric
- Tracing
- 内置观测性服务
- 高性能
字节内部 Kitex 实践分享
- Kitex 整体架构
- 自研网络库 Netpoll
-
性能优化:
- 网络库优化
- 编解码优化
- 合并部署
课中
基本概念
-
相比本地函数调用,RPC调用需要解决的问题
- 函数映射
- 数据转换成字节流
- 网络传输
- 一次 RPC 的完整过程
-
RPC 带来的问题将由 RPC 框架来解决
- 服务宕机如何感知?
- 遇到网络异常应该如何应对?
- 请求量暴增怎么处理?
RPC 框架分层设计
编解码层
-
数据格式
- 语言特定格式:例如 java.io.Serializable
- 文本格式:例如 JSON、XML、CSV 等
- 二进制编码:常见有 Thrift 的 BinaryProtocol,Protobuf,实现可以有多种形式,例如 TLV 编码 和 Varint 编码
-
选型考察点
-
兼容性
-
通用型
-
- 空间开销
- 时间开销
-
- 生成代码和编解码层相互依赖,框架的编解码应当具备扩展任意编解码协议的能力
协议层
-
以 Thrift 的 THeader 协议为例
- LENGTH 字段 32bits,包括数据包剩余部分的字节大小,不包含 LENGTH 自身长度 - HEADER MAGIC 字段16bits,值为:0x1000,用于标识 协议版本信息,协议解析的时候可以快速校验 - FLAGS 字段 16bits,为预留字段,暂未使用,默认值为 0x0000 - SEQUENCE NUMBER 字段 32bits,表示数据包的 seqId,可用于多路复用,最好确保单个连接内递增 - HEADER SIZE 字段 16bits,等于头部长度字节数/4,头部长度计算从第14个字节开始计算,一直到 PAYLOAD 前(备注:header 的最大长度为 64K) - PROTOCOL ID 字段 uint8 编码,取值有: - ProtocolIDBinary = 0 - ProtocolIDCompact = 2 - NUM TRANSFORMS 字段 uint8 编码,表示 TRANSFORM 个数 - TRANSFORM ID 字段 uint8 编码,表示压缩方式 zlib or snappy - INFO ID 字段 uint8 编码,具体取值参考下文,用于传递一些定制的 meta 信息 - PAYLOAD 消息内容
- 协议解析
网络通信层
- 阻塞 IO 下,耗费一个线程去阻塞在 read(fd) 去等待用足够多的数据可读并返回。
- 非阻塞 IO 下,不停对所有 fds 轮询 read(fd) ,如果读取到 n <= 0 则下一个循环继续轮询。
第一种方式浪费线程(会占用内存和上下文切换开销),第二种方式浪费 CPU 做大量无效工作。而基于 IO 多路复用系统调用实现的 Poll 的意义在于将可读/可写状态通知和实际文件操作分开,并支持多个文件描述符通过一个系统调用监听以提升性能。
网络库的核心功能就是去同时监听大量的文件描述符的状态变化(通过操作系统调用),并对于不同状态变更,高效,安全地进行对应的文件操作。
RPC 框架核心指标
稳定性
-
保障策略
- 熔断
- 限流
- 超时控制
从某种程度上讲超时、限流和熔断也是一种服务降级的手段 。
-
请求成功率
- 负载均衡
- 重试
-
长尾请求
- BackupRequest
易用性
-
开箱即用
- 合理的默认参数选项、丰富的文档
-
周边工具
- 生成代码工具、脚手架工具
扩展性
- Middleware:middleware 会被构造成一个有序调用链逐个执行,比如服务发现、路由、负载均衡、超时控制等
- Option:作为初始化参数
- 核心层是支持扩展的:编解码、协议、网络传输层
- 代码生成工具也支持插件扩展
观测性
- 三件套:Log、Metric 和 Tracing
-
内置观测性服务,用于观察框架内部状态
- 当前环境变量
- 配置参数
- 缓存信息
- 内置 pprof 服务用于排查问题
高性能
- 连接池和多路复用:复用连接,减少频繁建联带来的开销
- 高性能编解码协议:Thrift、Protobuf、Flatbuffer 和 Cap'n Proto 等
- 高性能网络库:Netpoll 和 Netty 等
字节内部 Kitex 实践分享
- 框架文档 Kitex
-
自研网络库 Netpoll,背景:
a. 原生库无法感知连接状态
b. 原生库存在 goroutine 暴涨的风险
- 扩展性:支持多协议,也支持灵活的自定义协议扩展
-
性能优化,参考 字节跳动 Go RPC 框架 KiteX 性能优化实践
a. 网络优化
i. 调度优化 ii. LinkBuffer 减少内存拷贝,从而减少 GC iii. 引入内存池和对象池
b. 编解码优化
i. Codegen:预计算提前分配内存,inline,SIMD等 ii. JIT:无生产代码,将编译过程移到了程序的加载(或首次解析)阶段,可以一次性编译生成对应的 codec 并高效执行
-
合并部署
a. 微服务过微,引入的额外的传输和序列化开销越来越大
b. 将强依赖的服务统计部署,有效减少资源消耗
课后
- 行业内各个流行的 RPC 框架的优劣对比
- 从第三章节 RPC 的核心指标来看,Kitex 还有哪些功能是欠缺或者需要加强的?
- 了解微服务的新趋势 ServiceMesh,以及 RPC 框架和 ServiceMesh 的关系
- 关于 RPC 框架,业界有哪些新的趋势和概念?
- Netpoll 的优势在哪?相比其他高性能网络库例如 Netty 还有什么不足?
- Flatbuffer 和 Cap'n Proto 等编解码协议为什么高性能?
参考文献
第十四节:HTTP 框架修炼之道
概述
本节课程主要分为四个方面:
- HTTP 协议相关知识
- HTTP 框架的设计与实现
- HTTP 框架的优化手段
- 企业实践
课前部分主要罗列课程中涉及到的概念。对于不熟悉的概念,同学们可以提前查询预习;课后部分是一些问题,帮助同学们在课后梳理本课程的重点。
课前
HTTP 协议
- HTTP 协议出现背景
- HTTP 协议是什么
- HTTP 协议有什么
可参考百度百科
尝试写一个 hello world 服务器
可尝试用 gin 写一个 hello world 程序,达到以下效果
HTTP 框架中常见概念
-
框架路由:根据请求的 URI 选择对应的处理函数。
-
首先匹配 HTTP 方法
-
静态路由: 精确匹配注册的路由,如:/a/b/c、/a/b/d
-
参数路由:
- 命名参数:形如
:name
这类叫做命名参数,命名参数只匹配单个路径段: -
``` Pattern: /user/:user
/user/gordon match(user = gordon) /user/you match(user = you) /user/gordon/profile no match /user/ no match
- 通配参数:形如 **`*action`**这类叫做通配参数,就像名字所暗示的那样,它们匹配所有内容。因此,它们必须始终位于模式的末尾: -
Pattern: /src/*filepath/src/ match(filepath = "") /src/somefile.go match(filepath = somefile.go) /src/subdir/somefile.go match(filepath = subdie/somefile.go) ```
- 命名参数:形如
-
路由修复: 如果只注册了 /a/b,但是访问的 URI 是 /a/b/,那可以提供自动重定向到 /a/b 能力;同样,如果只注册了 /a/b/,但是访问的 URI 是 /a/b,那可以提供自动重定向到 /a/b/ 能力
-
冲突路由:同时注册 /a/b 和 /:id/b,并设定优先级。比如:当请求 URI 为 /a/b 时,优先匹配静态路由 /a/b
-
Golang
- sync.Pool 用法
网络库
- C10K Problem
- Select,Poll,Epoll
- Epoll ET、LT 区别
- 字节跳动自研网络库 netpoll,netpoll-examples
SIMD
- SIMD 是什么,可参考维基百科
课后作业
- 为什么 HTTP 框架做要分层设计?分层设计有哪些优势与劣势。
- 现有开源社区 HTTP 框架有哪些优势与不足。
- 中间件还有没有其他实现方式?可以用伪代码说明。
- 完成基于前缀路由树的注册与查找功能?可以用伪代码说明。
- 路由还有没有其他的实现方式?
第十五节:微服务架构原理与治理实践
概述
本课程内容主要分为以下4个方面:
-
微服务架构介绍
- 微服务架构的背景由来、架构概览、基本要素
-
微服务架构原理及特征
- 微服务架构的基本组件、工作原理、流量特征
-
核心服务治理功能
- 核心的服务治理功能,包括流量治理、服务均衡、稳定性治理
-
字节跳动服务治理实践
- 字节跳动在微服务架构稳定性治理中,对请求重试策略的探索及实践
为了帮助大家更好地预习及理解本节课程,该学员手册列出了课前、课中、及课后这三个阶段所涉及到的专业内容大纲,其中课前部分供同学们提前预习参考,课中部分给出了课程大纲,帮助同学们整理思路,课后部分列出一些扩展性的问题让同学们进一步延伸思考。
课前
微服务架构介绍
-
系统架构的演进历史
- 单体架构
- 垂直应用架构
- 分布式架构
- SOA架构
- 微服务架构
-
微服务架构的三大要素
- 服务治理
- 可观测性
- 安全
微服务架构原理及特征
-
微服务架构中的基本概念及组件
- 服务、实例......
-
服务间通信
- RPC、HTTP
- 服务注册及服务发现
核心服务治理功能
-
服务发布
- 蓝绿部署
- 灰度发布(金丝雀发布)
- 流量治理
-
负载均衡
- Round Robin
- Ring Hash
- Random
-
稳定性治理
- 限流
- 熔断
- 过载保护
- 降级
字节跳动服务治理实践
- 请求重试的意义
- 请求重试的难点
课中
微服务架构介绍
系统架构的演进历史
-
单体架构
- All in one process
-
垂直应用架构
- 按照业务线垂直划分
-
分布式架构
- 抽出与业务无关的公共模块
-
SOA架构
- 面向服务
-
微服务架构
- 彻底的服务化
微服务架构概览
- 网关
- 服务配置和治理
- 链路追踪和监控
微服务架构的三大要素
-
服务治理(本课程内容)
- 服务注册
- 服务发现
- 负载均衡
- 扩缩容
- 流量治理
- 稳定性治理
-
可观测性
- 日志采集
- 日志分析
- 监控打点
- 监控大盘
- 异常报警
- 链路追踪
-
安全
- 身份验证
- 认证授权
- 访问令牌
- 审计
- 传输加密
- 黑产攻击
微服务架构原理及特征
微服务架构中的基本概念及组件
-
服务
- 一组具有相同逻辑的运行实体
-
实例
- 一个服务中的每个运行实体
-
实例与进程的关系
- 没有必然对应关系,一般一对一或者一对多
-
常见的实例承载形式
- 进程、VM、k8s pod......
服务间通信
- 微服务之间通过网络进行通信
- 常见的通信协议包括 HTTP、RPC
服务注册及服务发现
-
基本问题
- 服务间调用中,如何指定下游服务实例的地址?
-
简单方案
-
直接指定 ip:port?
- 没有任何动态能力
- 有多个实例下游实例怎么办?
-
使用 DNS?
- 本地 DNS 存在缓存,导致延迟
- DNS 没有负载均衡
- 不支持服务探活检查
- DNS 不能指定端口
-
-
服务注册发现
- 新增一个统一的服务注册中心,用于存储服务名到服务实例之间的映射关系
- 旧服务实例下线前,从服务注册中心删除该实例,下线流量
- 新服务实例上线后,在服务注册中心注册该实例,上线流量
-
微服务流量特征
- 统一网关入口
- 外网通信多数采用 HTTP,内网通信多数采用 RPC(Thrift, gRPC)
核心服务治理功能
服务发布
-
何为服务发布
- 让一个服务升级运行新的代码的过程
-
服务发布难点
- 服务不可用
- 服务抖动
- 服务回滚
-
蓝绿部署
- 将服务分成两个部分,分别先后发布
- 简单、稳定
- 但需要两倍资源
-
灰度发布(金丝雀发布)
- 先发布少部分实例,接着逐步增加发布比例
- 不需要增加资源
- 回滚难度大,基础设施要求高
流量治理
-
流量控制
- 在微服务架构中,可以从各个维度对端到端的流量在链路上进行精确控制
-
控制维度
- 地区维度
- 集群维度
- 实例维度
- 请求维度
负载均衡
- Round Robin
- Random
- Ring Hash
- Least Request
稳定性治理
-
限流
- 限制服务处理的最大 QPS,拒绝过多请求
-
熔断
- 中断请求路径,增加冷却时间从而让故障实例尝试恢复
-
过载保护
- 在负载高的实例中,主动拒绝一部分请求,防止实例被打挂
-
降级
- 服务处理能力不足时,拒绝低级别的请求,只响应线上高优请求
字节跳动服务治理实践
-
请求重试的意义
-
本地函数调用
- 通常没有重试意义
-
远程函数调用
- 网络抖动、下游负载高、下游机器宕机......
- 重试是有意义的,可以避免偶发性的错误,提高 SLA
-
重试的意义
- 降低错误率
- 降低长尾延时
- 容忍暂时性错误
- 避开下游故障实例
-
-
请求重试的难点
-
幂等性
- POST 请求可以重试吗?
-
重试风暴
- 随着调用链路的增加,重试次数呈指数级上升
-
超时设置
- 假设调用时间一共1s,经过多少时间开始重试?
-
-
重试策略
-
限制重试比例
- 设定一个重试比例阈值(例如 1%),重试次数占所有请求比例不超过该阈值
-
防止链路重试
- 返回特殊的 status code,表示“请求失败,但别重试”
-
Hedged Requests
- 对于可能超时(或延时高)的请求,重新向另一个下游实例发送一个相同的请求,并等待先到达的响应
-
-
重试效果验证
- 字节跳动重试组件能够极大限制重试发生的链路放大效应
课后
- 结合 CAP 等原理,思考微服务架构有哪些缺陷?
- 微服务是否拆分得越“微”越好?为什么?
- Service Mesh 这一架构是为了解决微服务架构的什么问题?
- 有没有可能有这样一种架构,从开发上线运维体验上是微服务,但实际运行又类似单体服务?