Revel 框架需手动注册 net/http/pprof handler 至其内置 ServeMux,不可仅靠 import;内存分析须加 ?gc=1&inuse_space=1 采样,并用 go tool pprof -base 对比两次快照;goroutine 泄漏常为根源,应优先通过 /debug/pprof/goroutine?debug=2 定位异常栈。

Revel 框架本身不内置 net/http/pprof,但它的 HTTP server 底层仍是 net/http,所以完全可以用标准 Go 的 pprof 方式接入——只要别误以为 Revel 有专属 profile 路由,也别漏掉关键参数。
为什么 Revel 默认不暴露 /debug/pprof?
Revel 启动的是自己的 router(revel.Server),不会自动挂载 net/http/pprof 的 handler。即使你写了 import _ "net/http/pprof",它只对直接用 http.ListenAndServe 的服务生效,对 Revel 的 revel.Run() 无效。
- 常见错误:只加 import,没注册路由 → 访问
/debug/pprof/返回 404 - 正确做法:在 Revel 的
app.Init()或app.Run()前,手动把 pprof 的 handler 注册进 Revel 的http.ServeMux - Revel 1.0+ 使用
revel.BuiltinHttpHandler管理底层 mux,需显式获取并注册:import "net/http/pprof" func init() { revel.InterceptFunc(func(c *revel.Controller, fc revel.FilterChain) revel.Result { // 在首次请求前注册一次即可 if !revel.IsDevMode && revel.HTTPServer != nil { mux := revel.HTTPServer.Handler.(*http.ServeMux) mux.Handle("/debug/pprof/", http.StripPrefix("/debug/pprof/", pprof.Handler())) mux.Handle("/debug/pprof/profile", pprof.ProfileHandler()) } return fc.Next(c) }, revel.BEFORE) }
采集内存快照必须带 ?gc=1&inuse_space=1
Revel 服务长期运行后堆内存增长,光 curl /debug/pprof/heap 得到的往往是“脏数据”:包含刚分配、尚未触发 GC 的临时对象,看不出真实泄漏。
- 必须加
?gc=1:强制 runtime 执行一次 GC,再抓存活对象 - 必须加
&inuse_space=1:确保返回的是当前驻留内存(不是历史总分配量allocs) - 推荐命令:
curl -s "http://localhost:9000/debug/pprof/heap?gc=1&inuse_space=1" > heap1.pb.gz
- 两次采样间隔建议 2–5 分钟;太短 GC 没跑完,太长干扰项太多
go tool pprof 分析时别跳过 -base 和 -sample_index
拿到两个 heap 快照后,直接 go tool pprof heap1.pb.gz 看不到泄漏点——默认显示的是单次快照的绝对值,而泄漏是相对增长。
- 查净增长:用
-base对比go tool pprof -base heap0.pb.gz heap1.pb.gz - 确认指标:默认用的是
inuse_space,但如果你之前没加&inuse_space=1,文件里可能只有alloc_space,得显式指定:go tool pprof -sample_index=inuse_space heap1.pb.gz - 进交互后第一件事是输
top,看inuse_space占比最高的函数;再输list 函数名定位到具体行 - 如果
top里全是runtime.mallocgc或bytes.makeSlice,说明你采样时没触发 GC 或没过滤临时分配,得重采
goroutine 泄漏常是内存泄漏的真正源头
Revel 中很多内存泄漏不是堆上对象没释放,而是 goroutine 钉住了引用:比如未关闭的 WebSocket 连接、未 cancel 的 context、或全局 map 里存了 controller 实例。
- 别只看 heap,先查
/debug/pprof/goroutine?debug=2(注意是?debug=2,不是1)→ 返回所有 goroutine 的完整栈,可 grep 关键字如websocket、select、chan receive - 对比
goroutine?debug=1(统计数)和?debug=2(详情),如果数字持续上涨且新 goroutine 栈里反复出现同一段逻辑,基本就是泄漏源 - Revel 的 filter 链、job runner、websocket handler 都容易成为泄漏温床,重点检查它们是否持有 controller、session 或 request 引用
pprof 不会告诉你“哪行代码写错了”,它只告诉你“哪些函数占着内存不放”。真正在 Revel 里定位泄漏,得把 goroutine profile 和 heap profile 交叉验证:先锁定异常增长的 goroutine,再顺着它的栈,去 heap 里找它分配/持有的对象。漏掉任一环,都可能花半天时间在错误方向上打转。











