这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
Day04——性能调优实战
性能调优的一些原则:
- 要根据实际开发环境和具体的数据分析,而不是主观的猜测
- 要专注定位对性能影响较大的瓶颈而不是细枝末节
- 最好不要过早优化,防止后续升级代码导致无法正常运行造成限制
- 不要过度优化
性能分析工具pprof优化
可以知道应用具体对CPU和内存的消耗,有可视化工具
功能简介图:
运行提前准备好的项目
浏览器输入,访问debug的网页
localhost:6060/debug/pprof/
网页中有一些指标和选项
CPU
可以在终端输入采样命令,此处采样10s
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
运行完成后,终端输入top命令,可以查看详细信息
flat 当前函数本身的执行耗时
flat% flat占 CPU总时间的比例
sum%上面每一行的flat%总和
cum指当前函数本身加上其调用函数的总耗时
cum%cum占CPU 总时间的比例
可以根据占比和时间长度找到限制程序效率的关键函数
终端输入list 【函数名】
可以查看具体函数中占用时间最长的代码,如下图,定位到for循环中的一步
使用web命令,可以自动打开网页有可视化调用关系和占比
Heap——堆内存
使用可视化命令
go tool pprof -http=:8080"http://localhost:6060/debug/pprof/heap"
打开后可以看到有VIEW选项
具体选项和刚刚在终端使用 的命令类似,比如top
Top视图
类似于top命令
Source视图
类似于终端list命令的结果
点开网页上的SAMPLE,可以看到四个选项
- alloc_objects:程序累计申请的对象数
- inuse objects:程序当前持有的对象数
- alloc_space:程序累计申请的内存大小
- inuse space:程序当前占用的内存大小
打开alloc_space,可以看到,有代码申请了大量额外的内存,这也可以对代码效率造成影响,而通过刚刚的排查,很可能会忽略掉
goroutine协程
使用时可以仿照刚刚的命令,只需要修改最后一个后缀即可
go tool pprof -http=:8080"http://localhost:6060/debug/pprof/goroutine"
打开后发现调用图非常长,不易分析
这次尝试使用flame graph火焰图去分析
从上到下表示调用顺序,每一块代表一个函数,块越长代表CPU占用时间越长,并且火焰图是动态的,可以点击对应函数块进行分析
mutex锁
命令类似前几步:
go tool pprof -http=:8080"http://localhost:6060/debug/pprof/mutex"
查看的步骤也和刚刚类似,看哪种视图方便就用哪种
block阻塞
go tool pprof -http=:8080"http://localhost:6060/debug/pprof/block"
操作类似
打开后发现先前最开始可以看到有2个阻塞,但是只显示了一个
原因就是,pprof会自动过滤一些时间极短的,防止展示内容过多影响分析,如果想看可以去首页点击对应的block链接查看
小结
标星处为需要调优时重点关注的地方
pprof采样过程和原理
对于CPU来说,采样对象:函数调用和它们占用的时间,采样率:100次/秒,固定值,采样时间:从手动启动到手动结束
流程图如下:
操作系统:每10ms向进程发送一次SIGPROF信号
进程:每次接收到SIGPROF会记录调用堆栈
写缓冲:每100ms读取已经记录的调用栈并写入输出流
对于Heap堆内存来说,采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量
采样率:每分配512KB记录一次,可在运行开头修改,1为每次分配均记录
采样时间:从程序运行开始到采样时
采样指标: alloc_space, alloc_objects, inuse_space, inuse_objects计算方式: inuse = alloc - freo
对于协程来说
Goroutine:记录所有用户发起且在运行中的 goroutine(即入口非runtime开头的)runtime.main的调用栈信息
ThreadCreate:记录程序创建的所有系统线程的信息
对于阻塞来说,采样阻塞操作的次数和耗时,采样率:阻塞耗时超过阈值的才会被记录,1为每次阻塞均记录
对于锁来说,采样争抢锁的次数和耗时,采样争抢锁的次数和耗时,采样率:只记录固定比例的锁操作,1为每次加锁均记录
性能调优案例
1.业务服务优化
基本概念:
服务:能单独部署,承载一定功能的程序
依赖:Service A 的功能实现依赖
Service B的响应结果,称为Service A依赖 Service B
调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
基础库:公共的工具包、中间件
优化流程:
建立服务性能评估手段-->分析性能数据,定位性能瓶颈-->重点优化项改造-->优化效果验证
建立服务性能评估手段
服务性能评估方式:单独benchmark无法满足复杂逻辑分析,不同负载情况下性能表现差异
请求流量构造:不同请求参数覆盖逻辑不同.线上真实流量情况
压测范围:单相器压测,集群压测(通常要借助于一些压测平台的压测报告去模拟)
性能数据采集:单机性能数据,集群性能数据
分析性能数据,定位性能瓶颈
使用库如果不规范,高并发场景高峰期使用优化会造成性能下降
重点优化项改造
必须优先保证程序的正确性,如果优化后改变不大,可能并不是关键影响程序的位置
优化效果验证
重复进行压测并且上线实际评估优化的效果,对比优化前数据看有没有根本性改变
进一步优化服务整体链路
比如规范接口,明确场景需求,是不是更小的数据集就可以满足需求,尽可能简洁调用,避免多次无意义多次调用
2.基础库优化
优化步骤也类似上面
1)分析基础库核心逻辑和性能瓶颈
设计完善改造方案
数据按需获取
数据序列化协议优化
2)内部压测验证
3)推广业务服务落地验证
3.GO语言本身的优化
优化内存分配
优化代码编译流程,生成更高效的程序
内部压测验证
推广业务服务落地验证