实操使用 go pprof 对生产环境进行性能分析(问题定位及代码优化)

简介

最近服务器有个小功能 go 进程 内存占用突然变得很高,正好使用 go pprof 实操进行性能分析排查解决

这是个极小的服务,但是占用内存超过了 100MB,而且本身服务器内存就比较吃紧,因此尝试使用 pprof 进行性能分析,看看能不能优化到 50 MB 以内

关于 pprof 的使用教程,请移步文章:Golang 性能分析神器 pprof 详解与实践(图文教程)

接下来使用 go pprof 进行实操性能分析排查

一、开启 pprof

在项目启动时开启 pprof

导包:_ "net/http/pprof"

启动:http.ListenAndServe(":30552", nil)

参考代码如下

go 复制代码
import _ "net/http/pprof"
func StartPprof() {
    go func() {
        logs.Info(http.ListenAndServe(":30552", nil))
    }()
    // 查看哪些函数占用内存比较多
    // go tool pprof -http=localhost:8081 -seconds=10 http://0.0.0.0:30552/debug/pprof/heap # 内存堆栈分析
    // go tool pprof -http=localhost:8081 -seconds=10 http://0.0.0.0:30552/debug/pprof/allocs # 内存分配分析

    // 查看哪些函数占用耗时比较多
    // go tool pprof -http=localhost:8081 -seconds=60 http://0.0.0.0:30552/debug/pprof/profile  # 耗时分析

    // go tool pprof -http=localhost:8081 -seconds=60 http://0.0.0.0:30552/debug/pprof/goroutine  # 协程分析
    // go tool pprof -http=localhost:8081 -seconds=10 http://0.0.0.0:30552/debug/pprof/block  # 阻塞分析
    // go tool pprof -http=localhost:8081 -seconds=10 http://0.0.0.0:30552/debug/pprof/mutex  # 锁分析
}
func main(){
  ....
  StartPprof()
  err = router.Run(address)
    if err != nil {
        panic(err)
    }
  ....
}

二、内存堆栈分析

分析哪些函数一直在占用内存

(一)问题分析

bash 复制代码
go tool pprof -http=0.0.0.0:8081 http://0.0.0.0:30552/debug/pprof/heap

这里我没有加 -seconds ,表示采样数据为:进程启动以来的总数据, 具体参数的使用:Golang 性能分析神器 pprof 详解与实践(图文教程)

浏览器会自动打开:0.0.0.0:8081/ui

如下图

从图中可以看到:又红又粗的函数有:

  • webdav(*memFile) Write

通过箭头查看上下的调用链接,可以发现 swaggerFiles init 调用了此函数,通过搜索查看代码,分析服务中确实是使用了 gin-swagger,但是这个项目其实没必要

解决:注释掉这块代码

  • embedFS

同样通过箭头查看上下的调用链接,发现有个 base64Captcha 调用,这是因为服务使用了一个第三方的验证码生成的包 github.com/mojocn/base64Captcha

embedFS 很明显是加载了文件,通过查看代码发现确实是加载了 fonts 字体

加载的字体大概有 5.9MB

解决:替换包,或者直接去掉验证码,我这里就选择一些比较轻量级的包做了替换

(二)总结

从火焰图上看,总体的内存堆栈不算太高,不过通过排查可以看到存在一些项目不需要的,但是有占用内存的函数/包,还是可以优化减少内存的占用,

通过以上的代码优化之后,重启并且观察一段时间后,内存确实稳定下降了一些

三、内存分配分析

分析哪些函数一直在分配内存

内存分配比较多比较频繁的函数会导致内存突然飙高,内存分配不同于内存堆栈,内存堆栈是常驻内存,

具体区别请移步文章:Golang 性能分析神器 pprof 详解与实践(图文教程

(一)问题分析

bash 复制代码
go tool pprof -http=0.0.0.0:8081 http://0.0.0.0:30552/debug/pprof/allocs

浏览器会自动打开:0.0.0.0:8081/ui

如下图

火焰图有点大,只截取了有问题的部分

从图中可以看到:又红又粗的函数有:

  • webdav(*memFile) Write + embedFS

    这里看不出来这两个函数的调用,我们可以切换为 top 视图(左上角菜单 view 中),就可以看到完整文件位置。

    解决:这个在上一步骤【内存堆栈分析】中就已经做了分析处理(注:代码发布后确实已修复)

  • DBUpdateCronTask

    这里很有可能是索引没有生效,导致锁全表了

    解决:通过对 update where 条件进行索引优化即可

  • go.Marshal

    这里用 go 标准库的 json 序列化操作,看起来分配内存也不算太多,不过我们可以优化为第三方包,性能更佳

    解决:全局替换包 encoding/jsongithub.com/json-iterator/go

  • fmt.Sprintf

通过排查代码,发现 FmtContent 中使用 fmt.Sprintf ,并且做了大量字符串的拼接,

解决:字符串拼接优化为使用 :strings.Builder

关于字符串拼接性能请移步文章:Go语言字符串拼接性能对比与最佳实践 - 深度优化指南

(二)总结

从火焰图上看,总体的内存分配不算太高,不过仍然有优化空间

通过以上的代码优化之后,重启并且观察一段时间后,内存成功下降到了 50MB 以内

四、CPU 耗时分析

这里从 top 可以看出其实并没有什么 CPU 飙高的情况,不过既然都优化了,就顺便看看哪些函数在占用吧

(一)问题分析

bash 复制代码
go tool pprof -http=0.0.0.0:8081 http://0.0.0.0:30552/debug/pprof/profile

从火焰图上没看到什么耗时比较异常的函数

(二)总结

从 top 没看到CPU异常,火焰图也没有异常,如果存在异常,排查和优化的方法和前面内存的步骤是一样的

五、协程分析

分析程序开启的协程情况

(一)问题分析

浏览器直接打开以下命令

bash 复制代码
http://0.0.0.0:30552/debug/pprof/goroutine?debug=1  # 协程总览
http://0.0.0.0:30552/debug/pprof/goroutine?debug=2  # 协程详情

界面展示说明如下:

从上面界面的数据进行分析

  1. 协程总数量为:27(不算多,但是需要看下每个协程的堆栈情况)

  2. 从 debug=2 的信息中查看每个协程的情况,发现其他都是正常的,有 main、 pprof 、cron、业务等启动的协程

    但是存在一个协程【44】:从堆栈上看,这并不是我服务所需的,通过查看这个堆栈定位到代码的地方,发现之前做链路追踪测试时确实导入这个 go.opentelemetry.io

    但是实际没有任何功能作用

    解决:直接注释掉相关的代码和导包

(二)总结

通过界面先查询协程数量是不是太多,过多可能存在泄漏的情况,再通过 debug=2 界面查看每个协程的执行情况,是否非业务所需但是仍旧执行/阻塞,

然后根据堆栈定位代码,然后优化即可

总结

通过启动 pprof 服务,并且通过 go tool pprof 以及 pprof 内部提供的服务接口对 CPU、内存堆栈、内存分配、协程 等指标进行分析,定位问题函数,优化代码

定位问题函数时,优化后可以先本地做单元测试、压力测试、pprof 测试,具体步骤请查看文章:

go 如何进行 Benchmark 基准测试

Golang 性能分析神器 pprof 详解与实践(图文教程)

原文地址

实操使用 go pprof 对生产环境进行性能分析(问题定位及代码优化)

相关推荐
Tony Bai1 小时前
【Go开发者的数据库设计之道】07 诊断篇:SQL 性能诊断与问题排查
开发语言·数据库·后端·sql·golang
卓码软件测评5 小时前
第三方软件验收测试:【AutoIt与Selenium结合测试文件上传/下载等Windows对话框】
windows·功能测试·selenium·测试工具·性能优化·可用性测试
JavaPub-rodert8 小时前
用 go-commons 打造更优雅的字符串处理工具
开发语言·后端·golang
SHtop118 小时前
排序算法(golang实现)
算法·golang·排序算法
Vahala0623-孔勇9 小时前
微服务接口性能优化终极指南:从HTTP/2多路复用到gRPC选型,序列化性能一网打尽
http·微服务·性能优化
sophie旭10 小时前
一道面试题,开始性能优化之旅(1)-- beforeFetch
前端·性能优化
维度攻城狮16 小时前
C++中的多线程编程及线程同步
开发语言·c++·性能优化·多线程·线程同步
孟意昶19 小时前
Spark专题-第三部分:性能监控与实战优化(2)-分区优化
大数据·分布式·sql·性能优化·spark·big data
zzywxc78719 小时前
AI赋能千行百业:金融、医疗、教育、制造业的落地实践与未来展望
java·人工智能·python·microsoft·金融·golang·prompt
FogLetter1 天前
前端性能救星:虚拟列表原理与实现,让你的10万条数据流畅如丝!
前端·性能优化