使用Prometheus计算百分位数值

年初的时候写过一篇文章《Prometheus + Grafana构建云时代的monitoring解决方案》,概要地讲述了monitoring系统的解决方案以及技术栈。本文则是深入描述如何用prometheus计算百分位数值。阅读本文之前,建议先简要了解Prometheus的基本概念。

Prometheus与其它项目的简单对比

进入正文之前,先简要对比一下Prometheus与其它类似项目。Prometheus官网从特性以及技术层面进行了对比,具体可以参考以下链接:

https://prometheus.io/docs/introduction/comparison/

但我这里只是从一个更直观的角度进行对比。因为各个项目都是github上的开源项目,所以只需要对比它们各自被关注的程度,就可以快速判断出各自的流行程度。这些数据是不断变化的,下面的数据是本文写作时统计的,

Graphite由Carbon和Whisper组成,所以分别列出了两者的数据。很明显,Prometheus全方位领先于其它项目。一个项目更流行,意味着使用者更多,也意味者生态系统更完善。我们在做技术选型时,这些数据往往可以给我们提供直观的依据。

百分位数(quantile)

Prometheus中称为quantile,其实叫percentile更准确。百分位数是指小于某个特定数值的采样点达到一定的百分比。例如,假设0.9-quantile的值为120,意思就是所有的采样值中,小于120的采样值的数量占总体采样值的90%。相应的,假设0.5-quantile的值为x,那么意思就是指小于x的采样值占总体的50%,所以0.5-quantile也指中值(median)。

相对于简单的平均值来说,百分位数更丰富,更能反应出真实的用户体验。常用的百分位数为0.5-quantile,0.9-quantile以及0.99-quantile。这也是Prometheus默认的设置。

注:这只是Prometheus中Summary目前版本的默认设置,在版本v0.10中,这些默认值会废弃,意味着默认的Summary将没有quantile设置。

用Summary计算quantile

Summary是Prometheus在client端支持的四种metrics类型之一。每定义一个Summary类型的metrics,实际会生成几个metrics。例如,下面的summary是用于监控http请求的响应时间,

httpCallDurations = prometheus.NewSummaryVec(

prometheus.SummaryOpts{

Name: “http_request_duration_millisecond",

Help: “http request durations in millisecond.",

Objectives: map[float64]float64,

},

[]string{“path"})

上面的summary实际上会生成5类metrics,分别如下。后面三个就是百分位数值。

http_request_duration_millisecond_count

http_request_duration_millisecond_sum

http_request_duration_millisecond

http_request_duration_millisecond

http_request_duration_millisecond

眼尖的同学可能已经注意到每个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)范围内的某个值。

之所以要设置误差,原因很简单,就是用一定的误差换取内存空间和CPU计算能力。换句话说,如果不允许有误差,Prometheus为了计算精确的百分位数,需要缓存并处理所有的采样值,从而可能要消耗大量的内存和计算资源,而设置一定了误差,则可以减缓内存和CPU的消耗。

使用Summary要注意一点,就是不能对Summary产生的quantile值进行aggregation运算(例如sum, avg等)。例如有两个实例同时运行,都对外提供服务,分别统计各自的响应时间。最后分别计算出的0.5-quantile的值为60和80,这时如果简单的求平均(60+80)/2,认为是总体的0.5-quantile值,那么就错了。如果你闭上眼睛,简单思考一下,就会明白对两个quantile值求平均毫无意义。所以如果需要对多个实例的quantile值进行aggregation操作,那么就不能使用Summary。

用Histogram计算quantile

Histogram也是Prometheus在client端支持的四种metrics类型之一。与Summary类似,每定义一个Histogram类型的metrics,实际也会生成几个metrics。例如,

metric := prometheus.NewHistogramVec(prometheus.HistogramOpts{

Name: " http_request_duration_millisecond",

Help: “http request durations in millisecond.",

Buckets: []float64,

}, []string{“path”})

上面的Histogram会产生下面6类metrics。后面4个可以用于计算quantile值。

http_request_duration_millisecond_count

http_request_duration_millisecond_sum

http_request_duration_millisecond_bucket

http_request_duration_millisecond_bucket

http_request_duration_millisecond_bucket

http_request_duration_millisecond_bucket

Histogram主要是设置不同的bucket,采用值分别落入不同的bucket。例如上面第一个bucket就是响应时间小于10ms的采样点的数量,第二个bucket就是响应时间小于50ms的采样点的数量,依此类推。

注意后面的采样点是包含前面的采用点的,例如xxx_bucket的值为30,而xxx_bucket的值为120,那么意味着这120个采用点中,有30个是小于10ms的,其余90个采样点的响应时间是介于10ms和50ms之间的。

注意+Inf是最高bucket的上限值,所以xxx_bucket是所有采样点的数量,是Prometheus自动增加的一个bucket。

计算quantile值直接用函数histogram_quantile即可,例如下面是计算0.9-quantile的值,

histogram_quantile(0.9, rate(http_request_duration_milliseconds_bucket[10m]))

上面会针对每种label组合计算出一个出一个0.9-quantile值,也就是对每个"path"会计算出一个值。如果要针对所有path计算出一个汇总的值,则用如下语句,

histogram_quantile(0.9, sum(rate(http_request_duration_milliseconds_bucket[10m])) by (le))

使用Histogram计算quantile值,最大的问题就是:因为Histogram采用了线性插值法,所以如果bucket设置不合理,那么最后计算出的值可能偏差比较大。例如在前面的例子中,假设0.9-quantile的结果在10ms~50ms之间,但是表达式必须返回一个具体的值,这时就采用线性插值法得出36ms。显然这种方法计算出的值可能会有误差,而且范围越大,例如10ms ~ 500ms,那么误差也会越大。

(50-10)*0.9=36ms

Summary和Histogram对比

根据前面分别对Summary和Histogram的描述,很显然Summary和Histogram计算quantile有很大的差别。

另外,它们之间一个重要的区别在于,Summary对quantile的计算是在client端完成的,而Histogram对quantile的计算是在server端完成的。这里client端是指使用了prometheus client library的模块。server端自然是指prometheus server。

分析client library中对Summary的实现源码,不难发现summary对quantile的计算是依赖第三方库perk实现的,

github.com/beorn7/perks/quantile

而perks采用的算法主要来自下面的一篇论文,对算法感兴趣的同学可以自行研究,

http://www.cs.rutgers.edu/~muthu/bquant.pdf

而Histogram对quantile的计算是在prometheus server端进行的,也就是前面讲的对histogram_quantile函数的计算是在server端完成的。所以很显然,client端处理summary的消耗比Histogram大,server端则正好反过来。

结合前面的描述,Summary和Histogram对quantile的处理的区别可以总结如下:

Summary不能对quantile值进行aggregation操作,而Histogram则可以;所以如果针对多实例的场景计算quantile,只能使用Histogram;

如果histogram的bucket设置不合理,则最后误差可能会很大;所以如果需要相对精确的结果,而且是单实例场景,那么就使用Summary;

Summary对quantile的计算是在client端通过第三方库perks做的;而Histogram对quantile的计算则是server端完成的;

Summary计算出的quantile值是基于进程开始运行至今的所有采样值计算出来的;而Histogram则是基于最近的一段时间的采样值计算出来的,更符合monitoring系统的本质。

--End--

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180922G14VJK00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券