Gin pprof性能分析
pprof 是 Go 标准库提供的性能分析工具,可用于 CPU、内存、Goroutine 等多维度分析。
pprof 集成
基础集成
Go
import (
"net/http/pprof"
"github.com/gin-gonic/gin"
)
func SetupPprof(r *gin.Engine) {
// 注册 pprof 路由
r.GET("/debug/pprof/", gin.WrapF(pprof.Index))
r.GET("/debug/pprof/cmdline", gin.WrapF(pprof.Cmdline))
r.GET("/debug/pprof/profile", gin.WrapF(pprof.Profile))
r.GET("/debug/pprof/symbol", gin.WrapF(pprof.Symbol))
r.GET("/debug/pprof/trace", gin.WrapF(pprof.Trace))
// 手动注册 heap 和 goroutine
r.GET("/debug/pprof/heap", gin.WrapH(pprof.Handler("heap")))
r.GET("/debug/pprof/goroutine", gin.WrapH(pprof.Handler("goroutine")))
r.GET("/debug/pprof/block", gin.WrapH(pprof.Handler("block")))
r.GET("/debug/pprof/threadcreate", gin.WrapH(pprof.Handler("threadcreate")))
}
func main() {
r := gin.Default()
// 仅在开发环境启用
if gin.Mode() == gin.DebugMode {
SetupPprof(r)
}
// 业务路由
r.GET("/api/data", DataHandler)
r.Run(":8080")
}
使用 gin-contrib/pprof
Bash
go get github.com/gin-contrib/pprof
Go
import "github.com/gin-contrib/pprof"
func main() {
r := gin.Default()
// 自动注册所有 pprof 路由
pprof.Register(r)
r.Run(":8080")
}
CPU 分析
采集 CPU Profile
Bash
# 通过 HTTP 采集 30 秒 CPU 数据
curl http://localhost:8080/debug/pprof/profile?seconds=30 > cpu.prof
# 使用 go tool pprof 分析
go tool pprof cpu.prof
# 交互式分析
(pprof) top10 # 查看 CPU 占用最高的函数
(pprof) list Handler # 查看特定函数详情
(pprof) web # 生成火焰图(需要 graphviz)
代码中采集
Go
import (
"runtime/pprof"
"os"
)
func StartCPUProfile() error {
f, err := os.Create("cpu.prof")
if err != nil {
return err
}
return pprof.StartCPUProfile(f)
}
func StopCPUProfile() {
pprof.StopCPUProfile()
}
// 使用示例
func main() {
StartCPUProfile()
defer StopCPUProfile()
// 运行应用或测试
}
CPU 分析示例
Go
// 启动服务
r.Run(":8080")
# 压测触发 CPU 使用
wrk -t4 -c100 -d30s http://localhost:8080/api/data
# 同时采集 profile
curl http://localhost:8080/debug/pprof/profile?seconds=30 > cpu.prof
# 分析
go tool pprof -http=:8081 cpu.prof # 打开 Web UI
内存分析
采集 Heap Profile
Bash
# 获取当前内存状态
curl http://localhost:8080/debug/pprof/heap > heap.prof
# 分析
go tool pprof heap.prof
# 交互式查看
(pprof) top10
(pprof) list CreateHandler
代码中采集
Go
func DumpHeapProfile(filename string) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
return pprof.WriteHeapProfile(f)
}
// 定时采集
func ScheduleHeapDump() {
ticker := time.NewTicker(5 * time.Minute)
go func() {
for t := range ticker.C {
filename := fmt.Sprintf("heap_%s.prof", t.Format("20060102_150405"))
DumpHeapProfile(filename)
}
}()
}
内存分析示例
Go
// 模拟内存泄漏
func LeakHandler(c *gin.Context) {
// 全局缓存不断增长
globalCache = append(globalCache, make([]byte, 1024*1024))
c.String(200, "ok")
}
# 多次请求后采集
curl http://localhost:8080/debug/pprof/heap > heap.prof
# 分析内存分配
go tool pprof -alloc_space heap.prof
(pprof) top10
Goroutine 分析
采集 Goroutine Profile
Bash
# 获取当前 Goroutine 数量和状态
curl http://localhost:8080/debug/pprof/goroutine > goroutine.prof
# 分析
go tool pprof goroutine.prof
# 查看 Goroutine 数量
(pprof) top10
Goroutine 泄漏检测
Go
func LeakGoroutineHandler(c *gin.Context) {
// Goroutine 泄漏:启动后不退出
go func() {
for {
time.Sleep(time.Second)
// 无退出条件
}
}()
c.String(200, "ok")
}
# 多次调用后查看
curl http://localhost:8080/debug/pprof/goroutine?debug=1
# 查看所有 Goroutine 状态
go tool pprof goroutine.prof
(pprof) traces # 查看 Goroutine 创建路径
阻塞分析
启用阻塞分析
Go
import "runtime"
func main() {
// 设置阻塞分析采样率(每个阻塞事件都记录)
runtime.SetBlockProfileRate(1)
r := gin.Default()
pprof.Register(r)
r.Run(":8080")
}
采集 Block Profile
Bash
# 获取阻塞分析数据
curl http://localhost:8080/debug/pprof/block > block.prof
# 分析
go tool pprof block.prof
(pprof) top10
Mutex 分析
启用 Mutex 分析
Go
func main() {
// 设置 Mutex 采样率
runtime.SetMutexProfileFraction(1)
r := gin.Default()
pprof.Register(r)
r.Run(":8080")
}
采集 Mutex Profile
Bash
# Go 1.8+ 支持
curl http://localhost:8080/debug/pprof/mutex > mutex.prof
# 分析锁竞争
go tool pprof mutex.prof
(pprof) top10
Web UI 分析
Bash
# 启动 Web 界面分析
go tool pprof -http=:8081 cpu.prof
# 或在交互模式中
(pprof) web
# 可查看:
# - Top 消耗函数
# - 火焰图
# - 函数调用图
# - 源码标注
常用分析命令
Bash
# CPU 分析
go tool pprof -top cpu.prof
go tool pprof -list=Handler cpu.prof
go tool pprof -http=:8081 cpu.prof
# 内存分析(分配空间)
go tool pprof -alloc_space heap.prof
# 内存分析(持有空间)
go tool pprof -inuse_space heap.prof
# 对比两个 profile
go tool pprof -base=old.prof new.prof
# 查看函数调用链
go tool pprof -traces cpu.prof
pprof 端点说明
| 端点 | 说明 | 用途 |
|---|---|---|
| /profile | CPU Profile | 分析 CPU 占用 |
| /heap | 内存 Profile | 分析内存分配 |
| /goroutine | Goroutine Profile | 分析协程状态 |
| /block | Block Profile | 分析阻塞原因 |
| /mutex | Mutex Profile | 分析锁竞争 |
| /trace | Trace | 执行追踪 |
生产环境安全配置
Go
func SetupSecurePprof(r *gin.Engine) {
// pprof 路由组,添加认证
pprofGroup := r.Group("/debug/pprof")
pprofGroup.Use(AuthMiddleware()) // 需要认证
pprofGroup.Use(IPWhitelistMiddleware()) // IP 白名单
pprof.Register(pprofGroup)
}
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("X-Debug-Token")
if token != "your-secret-token" {
c.AbortWithStatus(403)
return
}
c.Next()
}
}
func IPWhitelistMiddleware() gin.HandlerFunc {
whitelist := []string{"127.0.0.1", "192.168.1.100"}
return func(c *gin.Context) {
ip := c.ClientIP()
allowed := false
for _, w := range whitelist {
if ip == w {
allowed = true
break
}
}
if !allowed {
c.AbortWithStatus(403)
return
}
c.Next()
}
}
定时采集策略
Go
func SetupAutoDump() {
go func() {
ticker := time.NewTicker(1 * time.Hour)
for {
select {
case <-ticker.C:
timestamp := time.Now().Format("20060102_150405")
// 采集 heap
heapFile := fmt.Sprintf("profiles/heap_%s.prof", timestamp)
DumpHeapProfile(heapFile)
// 采集 goroutine
goroutineFile := fmt.Sprintf("profiles/goroutine_%s.prof", timestamp)
DumpGoroutineProfile(goroutineFile)
}
}
}()
}
func DumpGoroutineProfile(filename string) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
return pprof.Lookup("goroutine").WriteTo(f, 0)
}
分析流程
text
1. 启动服务并注册 pprof
2. 进行压力测试触发性能问题
3. 采集 profile 数据
4. 使用 go tool pprof 分析
5. 定位问题函数
6. 优化代码
7. 重新测试验证
注意:生产环境必须对 pprof 端点进行访问控制,避免信息泄露。
要点总结
- CPU 分析:定位 CPU 密集函数,使用
go tool pprof cpu.prof - 内存分析:检测内存泄漏,使用
-alloc_space和-inuse_space - Goroutine 分析:检测协程泄漏,查看数量和状态
- 阻塞分析:
runtime.SetBlockProfileRate(1)启用 - Web UI:
-http=:8081启动可视化界面 - 安全配置:生产环境必须添加认证和白名单
📝 发现内容有误?点击此处直接编辑