pprof 接入与常见性能优化点

结论先行

gin 不自带 /debug/pprof,需要自己把标准库 net/http/pprof 的处理器挂到路由上(用 gin.WrapH/gin.WrapF),或单独起一个内网端口跑默认 mux。采集用 go tool pprof:CPU 用 profile?seconds=30,内存用 heap,阻塞/锁有对应端点。性能优化要先 pprof 定位再动手,别猜。

接入代码

import (
    "net/http/pprof"
    "github.com/gin-gonic/gin"
)

func registerPprof(r *gin.Engine) {
    p := r.Group("/debug/pprof") // 生产建议仅内网/开关控制
    p.GET("/", gin.WrapF(pprof.Index))
    p.GET("/cmdline", gin.WrapF(pprof.Cmdline))
    p.GET("/profile", gin.WrapF(pprof.Profile))
    p.GET("/symbol", gin.WrapF(pprof.Symbol))
    p.GET("/trace", gin.WrapF(pprof.Trace))
    p.GET("/allocs", gin.WrapH(pprof.Handler("allocs")))
    p.GET("/block", gin.WrapH(pprof.Handler("block")))
    p.GET("/goroutine", gin.WrapH(pprof.Handler("goroutine")))
    p.GET("/heap", gin.WrapH(pprof.Handler("heap")))
    p.GET("/mutex", gin.WrapH(pprof.Handler("mutex")))
}

func main() {
    r := gin.New()
    registerPprof(r) // 或仅在 debug 配置为 true 时调用
    _ = r.Run(":8080")
}

采样命令

go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30   # CPU
go tool pprof http://localhost:8080/debug/pprof/heap                 # 堆内存
go tool pprof http://localhost:8080/debug/pprof/goroutine            # goroutine 栈
# 交互内:top / list 函数名 / web;或用 -http=:8081 打开火焰图界面

Gin 常见优化点(结合 pprof 结论)

  • 中间件别做重活:每请求日志格式化、反射、大对象复制都进 profile 后可见;能懒加载就懒加载。
  • JSON 是热点:绑定与渲染都走 encoding/json;大结构体考虑按需字段、复用对象、必要时换更快的编解码库(先测再换)。
  • 减少分配:避免 handler 内 fmt.Sprintf 拼热路径字符串、避免无谓的闭包与接口装箱;用 -benchmem 的 benchmark 佐证。
  • 数据库/下游:连接池大小、慢查询、N+1 往往是真正瓶颈,pprof 只能帮你定位到“谁在等”,再配 DB 侧工具。
  • 路由匹配本身开销小,别把时间浪费在“把路由改短”上;静态资源/大文件交给 Nginx/CDN。
  • goroutine 端点能看泄漏(数量持续上涨不回落),配合第 12 章的 context 规范排查。

常见追问 / 记忆点

  • 追问:pprof 能上生产吗?答:可上但建议内网端口或 debug 开关控制:profile 采样本身有 CPU 开销,且暴露符号信息。
  • 追问:接口慢先看哪个图?答:CPU profile 找热点函数;怀疑阻塞/锁看 block/mutex;内存涨看 heap;goroutine 数涨看 goroutine。
  • 记忆点:gin.WrapX 挂 pprof → go tool pprof 采样 → 先定位再优化;热点多半在 JSON、日志、DB 而不是路由。
笔记加载中…