Go pprof性能调优

2021-03-16 00:35:12 Golang 766 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("/hlzblog_debug") // 这个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
}

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

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

/hlzblog_debug/profile:访问这个链接会自动进行 CPU profiling,持续 30s,并生成一个文件供下载
/hlzblog_debug/heap: Memory Profiling 的路径,访问这个链接会得到一个内存 Profiling 结果的文件
/hlzblog_debug/block:block Profiling 的路径
/hlzblog_debug/goroutines:运行的 goroutines 列表,以及调用关系

可视化

Step 1

如果你当初发现性能瓶颈的场景
能在本地通过 wrk 直接压测复现
那么你现在可以先 wrk 执行起来了

示例,我本地压测漫画列表 API

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

示例 如图 01


图 01

Step 2

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

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 数据读取
并对读取下载后的进行图形绘制
比如,本次我计划采集的是内存相关数据

方式1

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

示例在ubutu上 安装

apt install graphviz

安装好后就可以以图形化工具查看打点指标数据了
指定本地专门用来看打点数据的监听地址,比如 0.0.0.0:8081
本地的指标数据路由 http://192.168.56.110:2333/hlzblog_debug/heap
默认是 30s
你可以在 url 参数后面加queryString 设置采集时间,如 seconds=30

go tool pprof -http=0.0.0.0:8081 "http://192.168.56.110:2333/hlzblog_debug/heap?seconds=30"

这个过程简单描述下
pprof 工具会先打点下载对应指标的 profiling 数据
然后再通过本地读取对应 pb 数据即可绘制 UI, 如 图 02-1

图 02-1

刚刚命令中的链接 http://192.168.56.110:2333/hlzblog_debug/heap?seconds=30
此后就可以直接换成pb文件地址也可以直接看 UI 渲染解雇了 图 02-2

图 02-2

本地就可以访问 8081 端口呈现出 UI 效果
比较常用的就是火焰图
图 02-3 方式选中 VIEW-Flame Graph

图 02-3

释义

火焰图的 Y轴 表示cpu调用方法的先后(从root开始)
X轴表示在每个采样调用时间内
方法所占对应指标数据的百分比
越宽代表占据对应指标数据越多
通过火焰图我们就可以更清楚的找出对应指标数据占比较高的函数调用
然后不断的修正代码 重新采样 不断去优化

比如这里的指标数据是内存
我们可以看到

curl_avatar.(*Dao).SupplierList

这个方法,占用的内存较高
这时候就可以分析是否为内存瓶颈

同理,可以使用采集及其指标进行分析

方式2

这个一般是想把 方式1 中采集到的火焰图导出来

Part 1

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

Part 2

安装 go-touch

go get -v github.com/uber/go-torch
Part 3
# 打点记录 30秒 堆栈信息 
go-torch -u http://192.168.56.110:2333 --suffix=hlzblog_debug/heap  -t 30 --colors mem  -f mem.svg

其中
-u 表示对应指标数据采集地址
--suffix= 表示采集的数据路由
-t 表示采集秒数
-f 表示采集生成的对应 SVG数据 存储到哪里
--colors 表示生成的 SVG图片 主题样式

生成的文件,如 图 03所示


图 03

生成的内容,如 图 04所示

图 04

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