背景
当我们要记录请求在不同延迟下的次数时,通常使用 prometheus.NewHistogram。
prometheus.NewHistogram(prometheus.HistogramOpts{
Buckets: []float64{0.1, 10},
})
其中 buckets 都是预先定义的,有限的。
比如对于 bucket:
[1, 3, 10]
它将所有耗时超过 10 的请求都记录成 inf。
我们有这么一个需求,需要记录用户 operation 的延迟直方图。
用户 operation 的时间范围很大,从 1us -> 3min 都有可能,并且需要足够精确。
我们将时间分为六个范围,每个范围 30 个 bucket:
- 1us - 100us
- 100us - 1ms
- 1ms -10ms
- 10ms -100s
- 100s - 1min
- 1min - 10min
问题
也就是说,对每一个 operation, 我们都会创建 200+ 个 bucket,这会导致近千万 series。
而 prometheus 消耗的内存和 series 数量成正比,也就是上百 GB 的内存。
解决
我们需要尽可能的减少 bucket 的数量,降低 prometheus 内存使用。
动态直方图
我们给 prometheus.histogram 添加了一个 reset 接口,允许调整 bucket 数量:
func (h *histogram) ResetBucket(newBuckets []float64)
我们会根据每一个 operation,历史的最大最小值,来 expand bucket。
例子:
比如旧的 bucket
[1, 3, 10]
当我们遇到一个耗时超过 10 的请求, 比如 14, 我们就会扩张 bucket:
[1, 3, 10, 15]
坑点
如果我们在多台机器上部署服务,
那么不同机器上相同 operation 的 bucket 数量会不一致,
这会给 grafana 的展示带来困难。。。
grafana 展示
我们有一个 mock-prometheus 服务于去解决这个问题:
- 获取每一个 instance 的 bucket
sum(increase(operation_latency{operation=""}[1m]) by (le, operation, instance)
- 将 le 上所有 instance 的数量累计
- 计算直方图
同时 mock-prometheus 服务还会解决其他问题:
- grafana 展示直方图时,纵坐标从 le 变成有意义的值,比如 0 ~ 160 ms
- 不展示数量为 0 的 bucket
最终效果:
同时, mock-prometheus 还会代理 grafana 的 exemplar 功能:
将分位数延迟图和导致延迟的点关联起来:
- 对于 p50 线,我们返回最接近 p50 延迟的点。
- 对于 p99 线,我们返回最接近 p99 延迟的点。