实操使用 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 对生产环境进行性能分析(问题定位及代码优化)

相关推荐
GoGeekBaird4 小时前
我开源了 BeeWeave,给 AI Agent 搭一个越用越懂你的知识创作台
后端·github
犀利豆7 小时前
AI in Harness(四)
人工智能·后端
Hui Baby8 小时前
Spring Security
java·后端·spring
逝水无殇9 小时前
C# 运算符重载详解
开发语言·后端·c#
苏三说技术9 小时前
2026编程圈很火的10个Skills
后端
用户8356290780519 小时前
使用 Python 自动化 Excel 公式和函数:完整指南
后端·python
编程(变成)小辣鸡10 小时前
Spring事务失效场景
java·后端·spring
PinkSun10 小时前
MySQL 建表报 1030,能查不能建,排查 1 小时发现是我自己改了一行权限
后端
无相求码10 小时前
为什么 printf 可以接受任意数量参数?变长参数的底层真相
后端
郡杰10 小时前
Boot:MP|测验|结果封装|异常处理|前后联调|拦截器
后端