sla监控必须用histogramvec而非summaryvec,因其桶边界固定、支持跨实例聚合且重启不重置;桶需按sla目标(如200ms→覆盖至0.5s)合理设置,避免默认桶失真;时间戳须用time.since()并置于servehttp前一刻获取;状态码应通过包装responsewriter的status()方法获取;指标注册、抓取地址和metrics_path三者必须正确配置。

怎么用 HistogramVec 统计 P99 延时才准
SLA 监控的核心是验证“99% 请求 ≤ X 秒”,这必须靠 prometheus.HistogramVec,不能用 SummaryVec。Summary 在服务重启后分位数会重置,且 Prometheus 无法跨实例聚合;Histogram 的桶(Buckets)是固定边界,histogram_quantile() 查询结果稳定、可聚合。
常见错误是直接用默认桶:[]float64{.005, .01, .025, .05, .1, .2, .5, 1, 2.5, 5, 10, 15}——它前段太密、后段太松,实际延时若集中在 200ms–800ms,99% 会落到 le="1" 和 le="+Inf" 之间,导致 P99 恒为 1s,完全失真。
- SLA 目标是 200ms → 桶至少要覆盖到 0.5s,推荐:
[]float64{0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10} - 目标是 1s → 补上
2和5,避免大量请求挤在最大桶外 - 桶不是越多越好:超过 20 个桶会显著增加内存和抓取开销,Prometheus 存储压力陡增
中间件里打点,start 时间戳怎么取才不漂移
延时统计不准,80% 出在时间戳获取方式上。time.Now().Sub(start) 看似等价于 time.Since(start),但前者依赖系统时钟,NTP 校正时可能回跳,出现负值或突增毛刺;time.Since() 底层调用单调时钟(monotonic clock),天然免疫时钟漂移。
更隐蔽的坑是:start 不能在中间件入口就打,比如:
func badMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now() // ❌ 这里太早!HTTP Server 可能延迟读 body
next.ServeHTTP(w, r)
dur := time.Since(start).Seconds()
hist.Observe(dur)
})
}
真实处理起点是 next.ServeHTTP() 开始执行那一刻,否则会把连接复用、TLS 握手、甚至客户端慢发 body 的时间全算进去。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 正确写法:在
next.ServeHTTP()前一刻调用start := time.Now() - 务必用
defer包裹Observe(),确保 panic 时仍能打点 - 别在 goroutine 里调
hist.Observe()——Histogram虽并发安全,但时间戳已失效
状态码怎么拿才不会被 WriteHeader 多次调用搞乱
HTTP 中间件里,w.WriteHeader(500) 可能被多次调用(比如日志中间件、错误包装器),最终状态码以最后一次为准。直接从 WriteHeader() 参数取,会拿到中间态,导致 http_requests_total{status="500"} 虚高。
标准解法是包装 ResponseWriter,实现 Status() int 方法:
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
func (rw *responseWriter) Status() int {
if rw.statusCode == 0 {
return 200 // 默认
}
return rw.statusCode
}
然后在中间件末尾用 rw.Status() 获取真实状态码,传给 counter.WithLabelValues() 或用于 SLA 判断(如只统计 status=~"2..|3.." 的耗时)。
- 别用
r.Context().Value()传状态码——易被覆盖、无类型安全 - 不要依赖
http.Error()的副作用——它只是调一次WriteHeader(),不保证是最终值 - 如果用 Gin/Echo 等框架,优先查其官方
ResponseWriter封装,比如 Gin 的c.Writer.Status()
Prometheus 抓不到指标?先盯住这三个硬条件
指标注册了、/metrics 能 curl 通、Grafana 也连上了,但 rate(http_requests_total[1m]) 始终为 0 —— 问题大概率不在配置语法,而在三个物理层条件没满足:
-
prometheus.MustRegister()必须在http.ListenAndServe()之前执行,否则 handler 启动时指标还没注册,promhttp.Handler()返回空响应 - Prometheus 的
scrape_configs中static_configs.targets必须填服务真实监听地址,比如10.1.2.3:8080,而不是localhost:8080(容器内 localhost ≠ 宿主机) -
metrics_path必须显式写成"/metrics",哪怕默认值也是它——某些旧版 Prometheus 或代理会忽略隐式路径
最容易被忽略的是:Histogram 打点后,Prometheus 抓取到的是 _bucket、_sum、_count 三组样本,缺一不可。histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[1h])) 如果返回空,先 curl http://your-service/metrics | grep bucket 确认所有桶都存在,再查 Prometheus 是否漏采了 _sum 或 _count。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










