Prometheus 的客户端与服务端
- 客户端是提供监控指标数据的一端(如写的 exporter)。prometheus 提供了各种语言的客户端库,需要通过 Prometheus 客户端库把监控的代码放在被监控的服务代码中。当 Prometheus 获取客户端的 HTTP 端点时,客户端库发送所有跟踪的度量指标数据到服务器上。详情见客户库
- 服务端是指 prometheus server,拉取、存储和查询各种各种指标数据。
Histogram
Histogram 是柱状图,在 Prometheus 系统中的查询语言中,有三种作用:
- 对每个采样点进行统计(并不是一段时间的统计),打到各个桶 (bucket) 中
- 对每个采样点值累计和 (sum)
- 对采样点的次数累计和 (count)
度量指标名称: [basename] 的柱状图,上面三类的作用度量指标名称
[basename]_bucket {le=“上边界”}
, 这个值为小于等于上边界的所有采样点数量[basename]_sum
[basename]_count
示例
图 1:Histogram 数据表 |
---|
如上表,设置 bucket=[1,5,10],当实际采样数据如是采样点所示,Observe 表示采样点落在该 bucket 中的数量,即落在 [-,1] 的样点数为 2,即落在 [1,5] 的样点数为 3,即落在 [5,10] 的样点数为 1,write 是得到的最终结果(histogram 的最终结果 bucket 计数是向下包含的):
[basename]_bucket{le=“1”} = 2
[basename]_bucket{le=“5”} =3
[basename]_bucket{le=“10”} =6
[basename]_bucket{le=”+Inf”} = 6
[basename]_count =6
[basename]_sum =18.8378745
Histogram 并不会保存数据采样点值,每个 bucket 只有个记录样本数的 counter(float64),即 Histogram 存储的是区间的样本数统计值,因此客户端性能开销相比 Counter 和 Gauge 而言没有明显改变,适合高并发的数据收集。
计算分位数
Histogram 常使用 histogram_quantile
执行数据分析, histogram_quantile
函数通过分段线性近似模型逼近采样数据分布的 UpperBound(如下图),误差是比较大的,其中红色曲线为实际的采样分布(正态分布),而实心圆点是 Histogram 的 bucket 的分为数分别被计算为 0.01 0.25 0.50 0.75 0.95,这是是依据 bucket 和 sum 来计算的。当求解 0.9 quantile 的采样值时会用 (0.75, 0.95) 两个相邻的的 bucket 来线性近似。
图 2:Histogram 数据分布 |
---|
但是如果自己知道数据的分布情况,设置适合的 bucket 也会得到相对精确的分位数。一个示例的 PromQL 为:
[root@liqiang.io]# cat test.promql
histogram_quantile(0.50, sum(irate(rpc_server_latency_bucket{client_name="client", service_name="server"}[30s])) by (le))
Summary
因为 Histogram 在客户端就是简单的分桶和分桶计数,在 Prometheus 服务端基于这么有限的数据做百分位估算,所以的确不是很准确,Summary 就是解决百分位准确的问题而来的。Summary 直接存储了 quantile 数据,而不是根据统计区间计算出来的。Prometheus 的分位数称为 quantile,其实叫 percentile 更准确。百分位数是指小于某个特定数值的采样点达到一定的百分比。
Summary 是采样点分位图统计。 它也有三种作用:
- 在客户端对于一段时间内(默认是 10 分钟)的每个采样点进行统计,并形成分位图。(如:正态分布一样,统计低于 60 分不及格的同学比例,统计低于 80 分的同学比例,统计低于 95 分的同学比例)
- 统计班上所有同学的总成绩 (sum)
- 统计班上同学的考试总人数 (count)
带有度量指标的 [basename] 的 summary 在抓取时间序列数据展示。
- 观察时间的
φ-quantiles (0 ≤ φ ≤ 1)
, 显示为[basename]{分位数="[φ]"}
[basename]_sum
, 是指所有观察值的总和[basename]_count
, 是指已观察到的事件计数值
Summary 对 quantile 的计算是依赖 第三方库 perk 实现的:https://github.com/bmizerany/perks/tree/master/quantile,而这个库是基于论文:Effective Computation of Biased Quantiles over Data Streams 实现的,有兴趣可以阅读一下(到目前为止,我没看过)。
示例
设置 quantile={0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
# HELP prometheus_tsdb_wal_fsync_duration_seconds Duration of WAL fsync.
# TYPE prometheus_tsdb_wal_fsync_duration_seconds summary
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.5"} 0.012352463
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.9"} 0.014458005
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.99"} 0.017316173
prometheus_tsdb_wal_fsync_duration_seconds_sum 2.888716127000002
prometheus_tsdb_wal_fsync_duration_seconds_count 216
从上面的样本中可以得知当前 Prometheus Server 进行 wal_fsync
操作的总次数为 216 次,耗时 2.888716127000002s。其中中位数(quantile=0.5)的耗时为 0.012352463,9 分位数(quantile=0.9)的耗时为 0.014458005s,90% 的数据都小于等于 0.014458005s。
设置每个 quantile 后面还有一个数,0.5-quantile 后面是 0.05,0.9-quantile 后面是 0.01,而 0.99 后面是 0.001。这些是我们设置的能容忍的误差。0.5-quantile: 0.05 意思是允许最后的误差不超过 0.05。假设某个 0.5-quantile 的值为 120,由于设置的误差为 0.05,所以 120 代表的真实 quantile 是 (0.45, 0.55) 范围内的某个值。注意 quantile 误差值很小,但实际得到的分位数可能误差很大。
查看分位数时 Summary 和 Histogram 的选择
清楚几点限制:
- Summary 结构有频繁的全局锁操作,对高并发程序性能存在一定影响。Histogram 仅仅是给每个桶做一个原子变量的计数就可以了,而 Summary 要每次执行算法计算出最新的 X 分位 value 是多少,算法需要并发保护,会占用客户端的 cpu 和内存。
- 不能对 Summary 产生的 quantile 值进行 aggregation 运算(例如 sum, avg 等)。例如有两个实例同时运行,都对外提供服务,分别统计各自的响应时间。最后分别计算出的 0.5-quantile 的值为 60 和 80,这时如果简单的求平均 (60+80)/2,认为是总体的 0.5-quantile 值,那么就错了。
- Summary 的百分位是提前在客户端里指定的,在服务端观测指标数据时不能获取未指定的分为数。而 Histogram 则可以通过 promql 随便指定,虽然计算的不如 Summary 准确,但带来了灵活性。
- Histogram 不能得到精确的分为数,设置的 bucket 不合理的话,误差会非常大。会消耗服务端的计算资源。
两条经验
- 如果需要聚合(aggregate),选择 histograms。
- 如果比较清楚要观测的指标的范围和分布情况,选择 Histograms。如果需要精确的分位数选择 Summary。
Histogram 相关函数实现
histogram_quantile
[root@liqiang.io]# cat promql/quantile.go
func histogramQuantile(q float64, h *histogram.FloatHistogram) float64 {
... ...
rank -= count - bucket.Count
// TODO(codesome): Use a better estimation than linear.
return bucket.Lower + (bucket.Upper-bucket.Lower)*(rank/bucket.Count)