延迟指标应使用 histogram 而非 summary,因其按桶切片、聚合稳定、支持跨实例准确计算分位数;需显式定义 buckets、按 route/method 加 label、结合 trace ID 定位尖刺,并用 histogram_quantile 正确聚合。

延迟指标该用 histogram 还是 summary?
Golang 的 promhttp 默认暴露的 http_request_duration_seconds 是 summary 类型,但实际做 SLO 计算或 P99 对比时,它容易因分位数抖动导致告警误触发。真正适合延迟分析的是 histogram:它把请求耗时按桶(bucket)切片上报,服务端聚合稳定,支持跨实例、跨时间窗口的准确百分位计算。
- 用
prometheus.NewHistogram替代prometheus.NewSummary,显式定义buckets,比如[]float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5}(单位秒),覆盖毫秒到秒级常见延迟范围 - 在 HTTP 中间件里调用
histogram.Observe(time.Since(start).Seconds()),注意别漏掉 panic 路径——未结束的请求不会被计时,会导致桶统计偏少 - 不要复用同一个
histogram实例去打不同 endpoint 的延迟,得按route或method维度加 label,否则查询时无法下钻
如何查出某次延迟尖刺的具体调用链?
Prometheus 本身不存 trace ID,单靠 rate(http_request_duration_seconds_bucket[5m]) 只能看到“哪个接口变慢了”,但不知道是数据库慢、下游超时还是 GC 暂停。必须让指标和 trace 关联起来:
- Golang 服务启动时注入
otel.Tracer,在 HTTP handler 开头用span := tracer.Start(ctx, "http.handle"),并在 span 上设置span.SetAttributes(attribute.String("http.route", r.URL.Path)) - 同时把 trace ID 注入 Prometheus 标签:用
ctx.Value("trace_id")(或从 span context 提取)作为histogram.WithLabelValues(traceID)的一部分——注意别直接塞进 label,会爆炸性增加 series 数量;建议只对异常延迟(如 >1s)才额外打一个带 trace_id 的临时指标http_slow_request_trace{trace_id="xxx"} - 查询时先用
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[1h])) by (le, route))定位问题 route,再查对应时间段的http_slow_request_trace,拿到 trace_id 去 Jaeger 查完整链路
为什么 rate() 和 increase() 在延迟聚合里结果差很多?
rate() 是每秒速率,increase() 是绝对增量,但延迟指标是 histogram,它的 bucket 是累计计数器(counter)。直接对 _bucket 系列用 rate() 会把“每秒落在某桶里的请求数”当成功率算,而实际要的是“P99 耗时”,必须用 histogram_quantile() 包裹 rate():
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 错误写法:
rate(http_request_duration_seconds_bucket{route="/api/user"}[5m])→ 得到的是每秒各桶请求数,没意义 - 正确写法:
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{route="/api/user"}[5m])) by (le))→ 先按le分组求速率,再算分位数 - 注意
[5m]时间窗口不能太小(histogram_quantile 会插值失真;也不能太大(>30m),否则掩盖短期毛刺
Golang runtime 指标怎么和业务延迟对齐?
go_gc_duration_seconds 或 go_goroutines 异常升高时,业务延迟往往同步恶化,但两者时间轴未必对齐——Prometheus 抓取间隔、Go runtime stats 上报时机、GC pause 实际发生时刻存在错位。
- 在关键 handler 里手动记录
debug.ReadGCStats的LastGC时间戳,并以gc_last_timestamp_seconds指标暴露,单位为 Unix 秒,精度到纳秒 - 查询时用
timestamp(http_request_duration_seconds_bucket) - gc_last_timestamp_seconds计算延迟发生时刻距离上次 GC 的秒数,再结合rate(go_gc_duration_seconds_sum[5m]) / rate(go_gc_duration_seconds_count[5m])看平均 pause 时长 - 别依赖
go_memstats_gc_cpu_fraction做判断——它是累积比例,不是瞬时值,容易误判 GC 压力
真实延迟分析里最麻烦的从来不是查哪个指标,而是确认你看到的“P99 上升”到底来自哪一层:是 HTTP 解析慢、JSON 序列化卡住、context timeout 设置过短,还是底层 syscall 阻塞。每个环节都得有对应指标打点,且 label 设计要能正交下钻——比如同时按 route、status_code、upstream_service 切,否则一出问题就得翻日志猜。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










