这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天
一、性能分析工具pprof
pprof是Go的性能分析工具,在程序运行过程中,可以记录程序的运行信息,可以是CPU使用情况、内存使用情况、goroutine运行情况等,当需要性能调优或者定位Bug时候,这些记录的信息是相当重要。
-
是用于可视化和分析性能分析数据的工具。
-
以 profile.proto 读取分析样本的集合,并生成报告以可视化并帮助分析数据(支持文本和图形报告)。
-
profile.proto 是一个 Protocol Buffer v3 的描述文件,它描述了一组 callstack 和 symbolization 信息, 作用是表示统计分析的一组采样的调用栈,是很常见的 stacktrace 配置文件格式。
1.浏览器查看指标
- allocs:内存分配情况
- blocks:阻塞操作情况
- cmdline:程序启动命令及
- goroutine:当前所有goroutine的堆栈信息
- heap:堆上内存使用情况(同alloc)
- mutex:锁竞争操作情况
- profile: CPU占用情况
- threadcreate:当前所有创建的系统线程的堆栈信息
- trace:程序运行跟踪信息
1.CPU
命令:topN
查看占用资源最多的函数
- Flat:当前函数的占用
- Flat%: Flat占总量的比例
- Sum%:上面所有行的Flat%总和
- Cum (Cumulative):当前函数加上其调用函数的总占用
- Cum%: Cum占总量的比例
Cum-Flat得到的是函数中调用其他函数所消耗的资源,所以在函数中没有对其他函数进行调用时,Cum-Flat=O,也就是Flat=cum。相应地,函数中除了调用另外的函数,没有其他逻辑时,Flat=O。
当函数中没有调用其他函数时flat=cum,函数中只有调用其他函数的逻辑时flat=O
命令:list
根据指定的正则表达式查找代码行,并按行展示出每一行的占用。
命令:web
调用关系可视化
2.堆内存
可以通过-http=:8080参数,可以开启pprof自带的Web Ul,性能指标会以网页的形式呈现。
切换回Source视图,可以查看调用和具体的源码视图。
sample的四个指标:
3.协程
打开View菜单,切换到Flame Graph视图可以看到,刚才的节点被堆叠了起来。图中,自顶向下展示了各个调用,表示各个函数调用之间的层级关系每一行中,条形越长代表消耗的资源占比越多。显然,那些“又平又长”的节点是占用资源多的节点。
4.锁、阻塞
修改链接后缀,分别改成mutex和block,然后打开网页观察,即可发现对应的结果。
二、pprof采样过程和采样原理
1.CPU
CPU采样会记录所有的调用栈和它们的占用时间。
在采样时,进程会每秒暂停一百次,每次会记录当前的调用栈信息。汇总之后,根据调用栈在采样中出现的次数来推断函数的运行时间。你需要手动地启动和停止采样。每秒100次的暂停频率也不能更改。
这个定时暂停机制在unix或类unix系统上是依赖信号机制实现的。
每次“暂停”都会接收到一个信号,通过系统计时器来保证这个信号是固定频率发送的。
一共有三个相关角色:进程本身、操作系统和写缓冲。
启动采样时,进程向OS注册一个定时器,OS会每隔10ms向进程发送一个SIGPROF信号,进程接收到信号后就会对当前的调用栈进行记录。
与此同时,进程会启动一个写缓冲的goroutine,它会每隔100ms从进程中读取已经记录的堆栈信息,并写入到输出流。
当采样停止时,进程向OS取消定时器,不再接收信号,写缓冲读取不到新的堆栈时,结束输出。
2.堆内存
内存采样在实现上依赖了内存分配器的记录,所以它只能记录在堆上分配,且会参与Gc的内存,一些其他的内存分配。例如调用结束就会回收的栈内存、一些更底层使用cgo调用分配的内存,是不会被内存采样记录的。
它的采样率是一个大小,默认每分配512KB内存会采样一次,采样率是可以在运行开头调整的,设为1则为每次分配都会记录。
与CPU和goroutine都不同的是,内存的采祥是一个持续的过程,它会记录从程序运行起的所有分配或释放的内存大小和对象数量,并在采样时遍历这些结果进行汇总。
3.协程和线程创建
Goroutie采样会记录所有用户发起,也就是入口不是runtime开头的goroutine,以及main函数所在goroutine的信息和创建这些goroutine的调用栈。
他们在实现上丰常的相似,都是会在STW之后,遍历所有goroutine/所有线程的列表并输出堆栈,最后Start The World继续运行。这个采祥是立刻触发的全量记录,你可以 人通过比较两个时间点的差值来得到某一时间段的指标。
4.阻塞、锁
这两个采样记录的都是对应操作发生的调用栈、次数和耗时,不过这两个指标的采样率含义并不相同。
阻塞操作的采样率是一个“阈值”,消耗超过阈值时间的阻塞操作才会被记录,1为每次操作都会记录。锁竞争的采样率是一个“比例”,运行时会通过随机数来只记录固定比例的锁操作,1为每次操作都会记录。
它们在实现上也是基本相同的。都是一个“主动上报”的过程。
在阻塞操作或锁操作发生时,会计算出消耗的时间,连同调用栈一起主动上报给采样器,采样器会根据采样率可能会丢弃一些记录。
在采样时,采样器会遍历已经记录的信息,,统计出具体操作的次数、调用栈和总耗时。和堆内存一样,你可以对比两个时间点的差值计算出这段时间内的操作指标。
三、性能调优案例
1.基本概念
- 服务:能单独部署,承载一定功能的程序。
- 依赖:Service A的功能实现依赖Service B的响应结果,称为Service A依赖Service B。
- 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系。
- 基础库:公共的工具包、中间件。
2.流程
1.业务服务优化
- 建立服务性能评估手段
- 服务性能评估方式
- 单独benchmark无法满足复杂逻辑分析
- 不同载荷情况下性能表现差异
- 请求流量构造
- 不同请求参数覆盖逻辑不同
- 线上真实流量情况
- 压测范围
- 单机器压测
- 集群压测
- 性能数据采集
- 单机性能数据
- 集群性能数据
- 分析性能数据,定位性能瓶颈
- 使用库不规范
- 高并发场景优化不足
- 重点优化项改造
- 正确性是基础
- 响应数据diff
- 线上请求数据录制回放
- 新旧逻辑接口数据diff
- 优化效果验证
- 重复压测验证
- 上线评估优化效果
- 关注服务监控
- 逐步放量
- 收集性能数据
- 进一步优化,服务整体链路分析
- 规范上流服务调用接口,明确场景需求
- 分析链路,通过业务流程优化提升服务性能
2.基础库优化
- 分析基础库核心逻辑和性能瓶颈
- 设计完善改造方案
- 数据按需获取
- 数据序列化协议优化
- 内部压测验证
- 推广业务服务落地验证
3.Go语言优化
- 优化内存分配策略
- ·优化代码编译流程,生成更高效的程序内部压测验证
- 内部压测验证
- 推广业务服务落地验证