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

相关推荐
Victor3562 小时前
Redis(16)Redis的有序集合(Sorted Set)类型有哪些常用命令?
后端
Victor3562 小时前
Redis(17)如何在Redis中设置键的过期时间?
后端
你的人类朋友10 小时前
说说git的变基
前端·git·后端
阿杆10 小时前
玩转 Amazon ElastiCache 免费套餐:小白也能上手
后端
阿杆10 小时前
无服务器每日自动推送 B 站热门视频
后端
公众号_醉鱼Java11 小时前
Elasticsearch 字段膨胀使用 Flattened类型
后端·掘金·金石计划
JohnYan12 小时前
工作笔记 - CentOS7环境运行Bun应用
javascript·后端·容器
探索java12 小时前
Netty Channel详解:从原理到实践
java·后端·netty
追逐时光者12 小时前
2025 年全面的 C#/.NET/.NET Core 学习路线集合,学习不迷路!
后端·.net
ankleless14 小时前
Spring Boot 实战:从项目搭建到部署优化
java·spring boot·后端