Expvar 不适合直接监控 QPS,因其仅为只读变量导出机制,无计数器重置、时间窗口聚合及速率计算能力,仅适合调试时查看静态总量。

Expvar 为什么不适合直接监控 QPS
Go 的 expvar 是个只读变量导出机制,不是指标采集系统。它不支持计数器自动重置、不提供时间窗口聚合、也没有采样或速率计算能力。你往里面塞一个 int64 累加请求数,它只会一直涨——没法直接看出“每秒多少次”。硬用 expvar.NewInt("http_requests_total") 加完就完,后续得靠外部工具(比如 Prometheus 抓取后做 rate())才能算出 QPS。
- 常见错误现象:
curl http://localhost:8080/debug/vars返回"http_requests_total": 123456,但没人知道这是 1 分钟还是 1 小时的量 - 使用场景:仅适合调试时快速看总量、内存占用等静态快照,不适合 SLO 观测或告警
- 性能影响:零开销,但数据粒度太粗,容易误判服务健康状态
用 HTTP Middleware 实现带窗口的 QPS 统计
真正能反映实时负载的是滑动窗口计数器。别碰 expvar 的原始值,直接在请求路径里统计。推荐用 sync.Map + 时间戳分桶,或者更稳的 golang.org/x/time/rate 的 Limiter 做反向推算(但那是限流,不是监控)。正向统计建议自己维护一个每秒一个 key 的 map:
type QPSSampler struct {
mu sync.RWMutex
count map[int64]int64 // key: 秒级时间戳,value: 该秒请求数
}
<p>func (q *QPSSampler) Inc() {
sec := time.Now().Unix()
q.mu.Lock()
q.count[sec]++
q.mu.Unlock()
}- 参数差异:窗口长度由你清理逻辑决定,比如只保留最近 60 秒,就在定时器里删掉
time.Now().Unix() - 60之前的 key - 容易踩的坑:用
time.Now().Second()会丢精度(跨分钟归零),必须用Unix();高并发下不加锁会导致计数丢失 - 兼容性:纯标准库,无第三方依赖,Go 1.16+ 都行
把 QPS 指标挂到 /debug/vars 但不滥用 expvar
可以继续用 /debug/vars 这个端点,但别把原始计数器塞进去。应该暴露的是“当前 QPS 估算值”,也就是从上面 QPSSampler 里实时算出来的数字。用 expvar.Publish 注册一个自定义变量,类型是 expvar.Func:
expvar.Publish("http_qps_1m", expvar.Func(func() interface{} {
return float64(qpsSampler.GetLastMinuteQPS()) // 自己实现的滑动平均
}))- 常见错误现象:注册成
expvar.Int后忘记更新,值永远是 0 或初始值 - 使用场景:运维 curl 这个端点就能拿到近似 QPS,和已有 debug 工具链无缝集成
- 性能影响:每次访问都触发一次计算,但只要不查全量历史(只读最近 N 个 bucket),开销可控
Prometheus 是更靠谱的落地选择
如果你的服务已经跑在 Kubernetes 或有统一监控体系,别卡在 expvar 上。用 promhttp 暴露指标,配合 prometheus/client_golang 的 prometheus.NewCounterVec:
立即学习“go语言免费学习笔记(深入)”;
var httpRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP Requests",
},
[]string{"method", "status"},
)
func init() { prometheus.MustRegister(httpRequests) }- 为什么这样做:Prometheus 原生支持
rate(http_requests_total[1m]),结果稳定、可告警、能下钻 label - 容易踩的坑:没配
scrape_interval和evaluation_interval匹配,导致 rate 计算不准;或者 counter 没按 status 分维度,5xx 错误被平均掉了 - 兼容性:需要额外引入 client_golang,但 Go module 下管理干净,编译进二进制无运行时依赖
Expvar 的定位是开发期辅助,不是生产指标基础设施。真要盯 QPS,要么自己写轻量滑动窗口并谨慎暴露,要么一步到位上 Prometheus。中间路线——比如用 expvar 存原始计数再靠脚本轮询计算——反而最易出错、最难维护。










