后端Go框架设计与实现:
这是我参与【第五届青训营】伴学笔记创作活动的第6天。
1.RPC框架分层设计:
本地函数的调用
func main(){
var a = 2
var b = 3
result := calculate(a,b)
fmt.Println(result)
return
}
func calculate(x,y int){
z :=x*y
return z
}
一次RPC的完整过程:
IDL (Interface description language)文件:
IDL 通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信
生成代码:
通过编译器工具把 IDL 文件转换成语言对应的静态库
编解码:
从内存中表示到字节序列的转换称为编码,反之为解码,也常叫做序列化和反序列化
通信协议:
规范了数据在网络中的传输内容和格式。除必须的请求/响应数据外,通常还会包含额外的元数据
网络传输
通常基于成熟的网络库走 TCP/UDP 传输
RPC 的好处
- 单一职责,有利于分工协作和运维开发
- 可扩展性强,资源使用率更优
- 故障隔离,服务的整体可靠性更高
协议层-协议构造:
- LENGTH: 数据包大小,不包含自身
- HEADER MAGIC:标识版本信息,协议解析时候快速校验
- SEQUENCE NUMBER: 表示数据包的 seqID可用于多路复用,单连接内递增
- HEADER SIZE: 头部长度,从第14个字节开始计算一直到 PAYLOAD前
- PROTOCOL ID:编解码方式,有 Binary 和Compact 两种
- TRANSFORM ID: 压缩方式,如 zlib 和 snappy
- INFO ID: 传递一些定制的 meta 信息
- PAYLOAD: 消息体
2.RPC 关键指标分析与企业实践
稳定性 - 保障策略:
-
熔断:保护调用方,防止被调用的服务出现问题而影响到整个链路
-
限流:保护被调用方,防止大流量把服务压垮
-
超时控制:避免浪费资源在不可用节点上
-
1.框架通过中间件来注入各种服务治理策略,保障服务的稳定性
-
2.通过提供合理的默认配置和方便的命令行工具可以提升框架的易用性
-
3.框架应当提供丰富的扩展点,例如核心的传输层和协议层
-
4.观测性除了传统的 Log、Metric 和 Tracing 之外,内置状态暴露服务也很有必要
-
5.性能可以从多个层面去优化,例如选择高性能的编解码协议和网络库
Netpoll
- 解决无法感知连接状态问题: 引入 epoll 主动监听机制,感知连接状态
- 解决 goroutine 暴涨的风险: 建立 goroutine 池,复用 goroutine
- 提升性能: 引入 Nocopy Buffer,向上层提供 NoCopy 的调用接口,编解码层面零拷贝
性能优化 - 网络库优化
- 调度优化 (1)epoll_wait 在调度上的控制 (2)gopool 重用 goroutine 降低同时运行协程数
- LinkBuffer (1)读写并行无锁,支持 nocopy 地流式读写 (2)高效扩缩容 (3)Nocopy Buffer 池化,减少 GC
- Pool (1)引入内存池和对象池,减少 GC 开销
性能优化 - 编解码优化
Codegen:
- 预计算并预分配内存,减少内存操作次数,包括内存分配和拷贝
- Inline 减少函数调用次数和避免不必要的反射操作等
- 自研了 Go 语言实现的 Thrift IDL 解析和代码生成器,支持完善的 Thrift IDL 语法和语义检查,并支持了插件机制 - Thriftgo
JIT
- 使用 JIT 编译技术改善用户体验的同时带来更强的编解码性能,减轻用户维护生成代码的负担
- 基于 JIT 编译技术的高性能动态 Thrift 编解码器 - Frugal
合并部署:
- 中心化的部署调度和流量控制
- 基于共享内存的通信协议
- 定制化的服务发现和连接池实现
- 定制化的服务启动和监听逻辑