Go pprof性能调优

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

此工具主要是用来采样、分析各链路上的各种指标数据以分析应用性能瓶颈的
默认每 10ms 会采样一次指定指标都数据
采集的这个数据,称为对应指标画像数据
下文我们都称之为 profiling 数据

流程概览


图 1-1

注意事项

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

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

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

前期准备

引入采集路由的功能,本文以 gin 为示例

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 列表,以及调用关系

数据采集

指标介绍

采集数据前,先了解各项指标含义

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

营造压力环境

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

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

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

HTTP配置

调整 http 服务的 writeTimeout的时间
需要稍微超过采集数据的时间
比如,现在我计划采样 30s
记得多设置几秒,比如我这里多设置 5s

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

本次我计划采集的是 内存分配情况 指标数据

  • 对于采集时长的建议
    • 一般 2 ~ 5 秒就基本可以满足绝大多数场景了
    • 对于一个越大的应用【编译时间都要过10秒钟】来说,每秒采集的数据量也会大很多,尽量别采集太长时间,否则渲染成 UI 图,用浏览器展示的时候,会比较卡
  • 生产环境 pprof 功能最好直接打开,不让外网访问即可,方便快速排查线上问题

数据可视化

推荐方式

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

依赖工具

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

依赖 go 包

安装 go-touch
记得找一个有 go.mod 的目录 执行以下命令

go get -v github.com/uber/go-torch
go install github.com/uber/go-torch

理论上,这会安装到 GOPATH 下面的 bin 目录里
所以记得环境变量加下这个目录
示例 linux 系统配置

export PATH=$PATH:$GOPATH/bin

绘制图形

下载 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 动态查看

示例在 Ubuntu 上 安装

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

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