性能调优实战案例|青训营笔记

116 阅读5分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 4天

1 性能分析工具pprof

  • 分析-Profile:有网页、可视化终端两种方式
  • 工具-Tool:可以在runtime/pprof中找到源码,同时在http的标准库也对pprof做了封装,能在http服务中直接使用
  • 采样-Sample:采样程序运行时的CPU、堆内存、goroutine、锁竞争、阻塞调用和系统线程的使用数据
  • 展示-View:用户可以通过列表、调用图、火焰图、源码、反汇编等视图去展示采集到的性能指标

pprof实战

1、搭建实战项目

项目地址:github.com/wolfogre/go…

运行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"

image-20230129101245696.png

top命令

查看占用资源最多的函数

image-20230129102227172.png

flat: 当前函数本身的执行耗时 flat%:flat占CPU总时间的比例 sum%: 上面每一行的flat%总和 cum: 当前函数本身加上其周期函数的总耗时 cum%: cum占CPU总时间的比例

默认显示资源占用最高的10个函数,需要查看最高的N个函数,可以输入topN

Flat == Cum,说明函数中没有调用其他函数

Flat == 0,说明函数中只有其他函数的调用

list命令

根据指定的正则表达式查找代码行

image-20230129102649718.png

输入list Eat可以看出tiger.go中24行的循环出了问题

web命令

image-20230129105959384.png

web命令生成一张调用关系图,默认使用浏览器打开

除了每个节点的资源占用外,还会将其关系串起来

  • Heap堆内存

直接可视化的方法查看:

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"

image-20230129103719414.png

view -> source查看代码文件

image-20230129105723478.png SAMPLE下的几个参数说明:

alloc_objects: 程序累计申请的对象数

alloc_space: 程序累计申请的内存大小

inuse_objects: 程序当前持有的对象数

inuse_space: 程序当前占用的内存大小

  • goroutine-协程

goroutine泄漏也会导致内存泄漏

可视化的方法查看,输入以下命令,打开堆内存的占用情况:

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"

image-20230129110759931.png

火焰图:【常用】

由上到下标识调用顺序

每一块代表一个函数,越长代表占用CPU时间更长

火焰图是动态的,支持点击块进行分析

每一行中,条形越长代表占用资源越多

  • mutex-锁
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"

image-20230129111307720.png

发现存在一个锁操作,在source视图中可以定位到具体哪一行

  • block-阻塞

image-20230129112627627.png

采样过程和原理

  • 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

image-20230129113803111.png

  • Mutex

image-20230129113845488.png

2 性能调优案例

业务服务优化

服务:能单独部署,承载一定功能的程序

依赖:ServiceA的功能实现依赖serviceB的响应结果,称为A依赖B

调用链路:能支持一个接口请求的相关服务集合及相互之间的依赖关系

基础路:公共的工具包、中间件

image-20230129114407769.png

客户请求经过弯管转发,有不同的业务服务处理,业务服务可能依赖其他的服务,也可能依赖存储、消息队列等组件

流程

  • 建立服务性能评估手段

    • 服务性能评估方式
      • 单独benchmark无法满足复杂逻辑分析
      • 不同负载情况下性能表现差异
    • 请求流量构造
      • 不同请求参数覆盖逻辑不同
      • 线上真实流量情况
    • 压测范围
      • 单机器
      • 集群
    • 性能数据采集
      • 单机
      • 集群
  • 分析性能数据,定位性能瓶颈

    • 使用库不规范
    • 高并发场景优化不足
  • 重点优化项改造

    • 正确是基础
    • 响应数据diff
      • 线上请求数据录制回放
      • 新旧逻辑接口数据diff
  • 优化效果验证

    • 重复压测验证
    • 上线评估优化效果
      • 关注服务监控
      • 逐步放量
      • 收集性能数据
  • 进一步优化

    • 服务整体链路分析
      • 规范上游服务调用接口,明确场景需求
      • 分析链路,通过业务流程优化提升服务性能

基础库优化

  • 分析基础库核心逻辑和性能瓶颈
  • 内部压测验证
  • 推广业务服务落地验证

Go 语言优化

编译器&运行时优化

  • 优化内存分配策略
  • 优化代码编译流程,生成更高效的程序
  • 内部压测验证
  • 推广业务服务落地验证