expvar 不适合直接做实时性能监控,它只负责导出原始数据,不提供计算或聚合能力;需借助 expvarmon 等工具加工,或用 expvar.Func 暴露预计算的动态指标(如 QPS、P95 延迟)。

expvar 不适合直接做实时性能监控,它只负责“导出”,不负责“计算”或“聚合”。真要看到 QPS、延迟 P95、每秒错误数这类指标,得靠外部工具(比如 expvarmon)拉取后加工,或者你自己在代码里实现滑动窗口逻辑再用 expvar.Func 暴露结果。
为什么 curl /debug/vars 看到的数字永远在涨
expvar 的核心是只读导出:你注册一个 expvar.Int,它就一直累加;注册一个 expvar.Float,它就原样返回当前值。没有自动重置、没有时间窗口、不区分“总量”和“速率”。
- 常见错误现象:
curl http://localhost:8080/debug/vars返回"http_requests_total": 421987,但没人知道这是启动后 2 小时还是 2 天的累计值 - 使用场景:适合调试时看“当前内存分配量”“goroutine 总数”这类静态快照,不适合 SLO 告警或 dashboard 展示
- 性能影响:零运行时开销,但数据粒度太粗,容易误判——比如
Goroutines突然从 100 到 120,到底是泄漏还是短时并发高峰?expvar 自己不告诉你
想暴露 QPS,别塞原始计数器,改用 expvar.Func
你要的不是“总请求数”,而是“最近 60 秒平均 QPS”。这必须自己算,然后用 expvar.Func 包一层,让 /debug/vars 返回动态值。
- 容易踩的坑:
- 用
expvar.NewInt("qps")然后手动更新——值永远滞后,且无法反映速率 - 在
expvar.Func里做耗时计算(比如遍历 map 求平均),会阻塞整个/debug/vars请求
- 用
- 正确做法:用
sync.Map存每秒计数,定时清理旧 key,expvar.Func只做轻量读取和简单除法 - 示例关键行:
expvar.Publish("http_qps_1m", expvar.Func(func() interface{} { return float64(qpsSampler.GetLastMinuteQPS()) }))
expvarmon 是最轻量的实时可视化方案
它不改你的代码,只消费 /debug/vars,把 JSON 里的数字变成终端里的 Sparkline 图表和滚动数值,适合本地调试和远程快速巡检。
- 启动命令极简:
expvarmon -ports="8080",端口通就能看 - 多服务监控:
expvarmon -ports="8080,8081,prod-server:8080",所有指标并排对比 - 自定义变量:
-vars="memstats.Alloc,Goroutines,http_qps_1m",只关注你真正关心的那几个 - 兼容性注意:如果 Go 应用启用了 Basic Auth,
expvarmon支持-auth="user:pass";但不支持 bearer token 或其他认证方式
真正难的不是暴露指标,而是定义清楚“你想回答什么问题”——是查内存泄漏?看 goroutine 是否失控?还是验证限流策略是否生效?expvar 只是管道,往里灌什么、怎么读,得由你决定。别让它替你做本该由业务逻辑完成的聚合计算。



















