prometheus 动态直方图

1,071 阅读2分钟

背景

当我们要记录请求在不同延迟下的次数时,通常使用 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

最终效果:

截屏2022-11-10 下午1.05.57.png

同时, mock-prometheus 还会代理 grafana 的 exemplar 功能:

将分位数延迟图和导致延迟的点关联起来:

  • 对于 p50 线,我们返回最接近 p50 延迟的点。
  • 对于 p99 线,我们返回最接近 p99 延迟的点。

截屏2022-11-10 下午1.07.18.png