这是我参与「第五届青训营」伴学笔记创作活动的第 4天
1 性能分析工具pprof
- 分析-Profile:有网页、可视化终端两种方式
- 工具-Tool:可以在runtime/pprof中找到源码,同时在http的标准库也对pprof做了封装,能在http服务中直接使用
- 采样-Sample:采样程序运行时的CPU、堆内存、goroutine、锁竞争、阻塞调用和系统线程的使用数据
- 展示-View:用户可以通过列表、调用图、火焰图、源码、反汇编等视图去展示采集到的性能指标
pprof实战
1、搭建实战项目
运行main.go之后,使用浏览器打开http://localhost:6060/debug/pprof,页面即为引入的net/http/pprof注入的页面
Count Profile:程序运行采样数据
16 allocs 内存分配情况
3 block 阻塞操作情况
0 cmdline 程序启动命令
66 goroutine 当前goroutine堆栈信息
16 heap 堆上内存使用情况
1 mutex 锁竞争操作情况
0 profile CPU占用情况
7 threadcreate 当前所有创建的系统线程的堆栈信息
0 trace 程序运行跟踪信息
2、排查实战
- CPU排查
使用go tool pprof + 采样链接来启动采样
在终端输入命令,获取文件:go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
top命令
查看占用资源最多的函数
flat: 当前函数本身的执行耗时 flat%:flat占CPU总时间的比例 sum%: 上面每一行的flat%总和 cum: 当前函数本身加上其周期函数的总耗时 cum%: cum占CPU总时间的比例
默认显示资源占用最高的10个函数,需要查看最高的N个函数,可以输入topN
Flat == Cum,说明函数中没有调用其他函数
Flat == 0,说明函数中只有其他函数的调用
list命令
根据指定的正则表达式查找代码行
输入list Eat可以看出tiger.go中24行的循环出了问题
web命令
web命令生成一张调用关系图,默认使用浏览器打开
除了每个节点的资源占用外,还会将其关系串起来
Heap堆内存
直接可视化的方法查看:
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"
view -> source查看代码文件
SAMPLE下的几个参数说明:
alloc_objects: 程序累计申请的对象数
alloc_space: 程序累计申请的内存大小
inuse_objects: 程序当前持有的对象数
inuse_space: 程序当前占用的内存大小
- goroutine-协程
goroutine泄漏也会导致内存泄漏
可视化的方法查看,输入以下命令,打开堆内存的占用情况:
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"
火焰图:【常用】
由上到下标识调用顺序
每一块代表一个函数,越长代表占用CPU时间更长
火焰图是动态的,支持点击块进行分析
每一行中,条形越长代表占用资源越多
- mutex-锁
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
发现存在一个锁操作,在source视图中可以定位到具体哪一行
- block-阻塞
采样过程和原理
-
CPU
- 采样对象:函数调用和其占用的时间
- 采样率:100次/s,固定值
- 采样时间:从手动启动到手动结束
- 过程
- 操作系统每10ms向进程发送一次SIGPROF信号
- 进程每次接收到SIGPROF会记录调用堆栈
- 写缓冲每100ms读取已经记录的调用栈并写入输出流
-
Heap堆内存
- 采样程序通过内存分配器在堆上分配和释放内存,记录分配/释放的大小和数量
- 采样率:每分配512KB记录一次,可在运行开头修改,1为每次分配均记录
- 采样时间:从程序运行开始到采样时
- 采样指标:alloc_space,alloc_objects,inuse_space,inuse_objects
- 计算方式:inuse = alloc - free
-
Goroutine-协程
- 记录所有用户发起且在运行中的goroutine、runtime.main的调用栈信息
-
ThreadCreate
- 记录程序创建的所有系统线程的信息
-
Block
- Mutex
2 性能调优案例
业务服务优化
服务:能单独部署,承载一定功能的程序
依赖:ServiceA的功能实现依赖serviceB的响应结果,称为A依赖B
调用链路:能支持一个接口请求的相关服务集合及相互之间的依赖关系
基础路:公共的工具包、中间件
客户请求经过弯管转发,有不同的业务服务处理,业务服务可能依赖其他的服务,也可能依赖存储、消息队列等组件
流程
-
建立服务性能评估手段
- 服务性能评估方式
- 单独benchmark无法满足复杂逻辑分析
- 不同负载情况下性能表现差异
- 请求流量构造
- 不同请求参数覆盖逻辑不同
- 线上真实流量情况
- 压测范围
- 单机器
- 集群
- 性能数据采集
- 单机
- 集群
- 服务性能评估方式
-
分析性能数据,定位性能瓶颈
- 使用库不规范
- 高并发场景优化不足
-
重点优化项改造
- 正确是基础
- 响应数据diff
- 线上请求数据录制回放
- 新旧逻辑接口数据diff
-
优化效果验证
- 重复压测验证
- 上线评估优化效果
- 关注服务监控
- 逐步放量
- 收集性能数据
-
进一步优化
- 服务整体链路分析
- 规范上游服务调用接口,明确场景需求
- 分析链路,通过业务流程优化提升服务性能
- 服务整体链路分析
基础库优化
- 分析基础库核心逻辑和性能瓶颈
- 内部压测验证
- 推广业务服务落地验证
Go 语言优化
编译器&运行时优化
- 优化内存分配策略
- 优化代码编译流程,生成更高效的程序
- 内部压测验证
- 推广业务服务落地验证