Go pprof性能调优

2021-03-16 00:35:12 Golang 1487 0

此工具主要是用来采样、分析各链路上的各种指标数据以分析应用性能瓶颈的
就目前而言 大概 10ms 会采样一次各项数据
采集的这个数据,下文我们称之为 profiling 数据

注意事项

获取的 profiling 数据是动态的
要想获得有效的数据,请保证应用处于较大的负载(比如正在生成中运行的服务,或者通过其他工具模拟访问压力)
否则如果应用处于空闲状态,得到的结果可能没有任何意义

如果是 API 性能问题
一般可以通过本地使用 wrk 进压测
或者 预发布环境 配合 access_log 进行 流量回放

请注意 pprof 相关接口,在生产环境,请在网关层禁止外部访问
以免被外面的人恶意使用

gin中的使用

引入包 net/http/pprof

package http

import (
    "github.com/gin-gonic/gin"
    "net/http/pprof"
    "node_puppeteer_example_go/api/service"
)

var srv *service.Service

func Init(e *gin.Engine, srvInjection *service.Service) *gin.Engine {
    srv = srvInjection

    //e.Use() // 暂无中间件需要被设置
    {
        comic := &Comic{}
        /**
         * 用户端API
         * TODO 接口级缓存
         */
        routeComic := e.Group("api/")
        routeComic.GET("comic/list", comic.GetList)
        routeComic.GET("chapter/list", comic.GetChapterList)
        routeComic.GET("chapter/detail", comic.GetChapterDetail)
        routeComic.GET("image/list", comic.GetImageList)
    }

    {
        // 注入debug信息到router
        routerDebug := e.Group("/debug/pprof") // 这个group的命名自定义即可
        routerDebug.GET("/", func(c *gin.Context) {
            pprof.Handler("index").ServeHTTP(c.Writer, c.Request)
        })
        routerDebug.GET("/heap", func(c *gin.Context) {
            pprof.Handler("heap").ServeHTTP(c.Writer, c.Request)
        })
        routerDebug.GET("/goroutine", func(c *gin.Context) {
            pprof.Handler("goroutine").ServeHTTP(c.Writer, c.Request)
        })
        routerDebug.GET("/allocs", func(c *gin.Context) {
            pprof.Handler("allocs").ServeHTTP(c.Writer, c.Request)
        })
        routerDebug.GET("/block", func(c *gin.Context) {
            pprof.Handler("block").ServeHTTP(c.Writer, c.Request)
        })
        routerDebug.GET("/threadcreate", func(c *gin.Context) {
            pprof.Handler("threadcreate").ServeHTTP(c.Writer, c.Request)
        })
        routerDebug.GET("/cmdline", func(c *gin.Context) {
            pprof.Handler("cmdline").ServeHTTP(c.Writer, c.Request)
        })
        routerDebug.GET("/profile", func(c *gin.Context) {
            pprof.Handler("profile").ServeHTTP(c.Writer, c.Request)
        })
        routerDebug.Any("/symbol", func(c *gin.Context) {
            pprof.Handler("symbol").ServeHTTP(c.Writer, c.Request)
        })
        routerDebug.GET("/trace", func(c *gin.Context) {
            pprof.Handler("trace").ServeHTTP(c.Writer, c.Request)
        })
        routerDebug.GET("/mutex", func(c *gin.Context) {
            pprof.Handler("mutex").ServeHTTP(c.Writer, c.Request)
        })
    }
    return e
}

当然,你也可以看看云天河在生产环境的集成示例 点此查看

其中我们通常需要观察的几个路由如下

/debug/pprof/profile:访问这个链接会记录 CPU profiling
/debug/pprof/heap: Memory Profiling 的路径,访问这个链接会得到一个内存 Profiling 结果的文件
/debug/pprof/block:block Profiling 的路径
/debug/pprof/goroutines:运行的 goroutines 列表,以及调用关系

可视化

让瓶颈场景,处于压力环境

现在发现哪个地方慢就让哪块儿逻辑处于压力状态
如果是 API 接口,可以直接通过压测尝试复现
本文使用 wrk 作为压测工具

示例场景
我现在认为 漫画列表API
那我就用工具压测吧

条件名 对应值 备注
线程数 8个 -
并发连接数 50个 -
压测时长 20分钟 主要是想边测边看
wrk -t8 -c50 -d 20m 'http://192.168.1.7:8100/api/comic/list?page=1' 

指标介绍

采集打点前,了解各项指标含义

router后缀 打点指标介绍
allocs 内存分配情况的采样信息
blocks 阻塞操作情况的采样信息
cmdline 显示程序启动命令及参数
goroutine 当前所有协程的堆栈信息
heap 存活对象内存使用情况的采样信息
mutex 锁争用情况的采样信息
profile CPU 占用情况的采样信息
threadcreate 系统线程创建情况的采样信息
trace 程序运行跟踪信息

调整 http 服务的 writeTimeout的时间
稍微超过读取 profiling 的时间即可
比如,现在我计划采样 30s
多设置几秒,比如我这里多设置 5s

httpServer:
  name: node_puppeteer_example_go_api
  ip: 0.0.0.0
  port: 2333
  readTimeout: 3s
  writeTimeout: 35s
  maxHeaderBytes: 1048576

在进行压测的期间,进行 profiling 数据读取
并对读取下载后的进行图形绘制
比如,本次我计划采集的是 内存 相关数据

推荐方式

依据 profiling 数据,生成SVG图像文件,方便传阅

依赖工具

下载生成图形的工具 FlameGraph
然后将其解压后的目录加入 环境变量

依赖 go 包

安装 go-touch

go get -v github.com/uber/go-torch
绘制图形

下载 profiling 数据到本地文件 raw.log

curl http://192.168.1.7:8100/debug/pprof/heap?seconds=30 > raw.log

通过 raw.log 生成图像文件 mem.svg

go-torch  -b ./raw.log -f mem.svg


生成的文件,如 图 3-1所示


图 3-1

生成的文件,就是火焰图,如 图 3-2所示

图 3-2

火焰图含义

  • X轴
    • 表示在每个采样调用时间内,方法所占对应指标数据的百分比
  • Y轴
    • 表示 CPU 调用方法的先后- 从root开始

一般我们关注 X轴 占比较高的,去优化相关资源消耗问题
图 2-1 ,中

api/service.(*Service).ComicList

占用的对应资源较高
这时候就可以分析是否为瓶颈

传统方式

这个过程简单描述下
pprof 工具会先打点下载对应指标的 profiling 数据
然后通过 Web 渲染 UI 展示

依赖工具

通过 pprof 配合图形化工具 graphviz 动态查看

示例在 Ubutu 上 安装

apt-get install graphviz -y
采集并展示
  • Web展示地址 0.0.0.0:8081
  • 采集指标地址 http://192.168.1.7:8100/debug/pprof/heap
  • 采集时长 默认 30s
    • GET 参数 seconds 可以设置采集秒数
go tool pprof -http=0.0.0.0:8081 "http://192.168.1.7:8100/debug/pprof/heap?seconds=30"

等待 30s 后,我们访问下对应地址
为了直观查看数据,选择 Flame Graph 别名 火焰图,如 图 4-1


图 4-1

注:若无特殊说明,文章均为云天河原创,请尊重作者劳动成果,转载前请一定要注明出处