前言
- prometheus 官网文档中对于两种类型的比照阐明
- 上面我总结一些比照点
比照点 | histogram | summary |
---|---|---|
查问表达式比照 | histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) |
http_request_duration_seconds_summary{quantile="0.95"} |
所需配置 | 抉择适合的 buckets | 抉择所需的 φ 分位数和滑动窗口。其余 φ 分位数和滑动窗口当前无奈计算。 |
客户端性能开销 | 开销低,因为它们只须要减少计数器 | 开销高,因为流式分位数计算 |
服务端性能开销 | 开销高,因为须要在服务端实时计算(而且 bucket 值指标基数高) | 开销低,能够看做是 gauge 指标上传,仅查问即可 |
分位值误差 | 随 bucket 精度变大而变大(线性插值法计算问题) | 误差在 φ 维度上受可配置值限度 |
是否反对聚合 | 反对 | 不反对(配置 sum avg 等意义不大) |
是否提供全局分位值 | 反对(依据 promql 匹配维度决定) | 不反对(因为数据在每个实例 /pod/agent 侧曾经算好,无奈聚合) |
histogram 线性插值法
histogram_quantile 为何须要先算rate
- 因为每个 bucket 都是
counter
型的,如果不算 rate 那么分位值的后果曲线是一条直线 - 原理是因为
counter
型累加,不算 rate 并不知道以后 bucket 的增长状况,换句话说不晓得这些 bucket 是多久积攒到当初这个值的
什么是线性插值法
- 之前浏览很多文章都提到
histogram
采纳线性插值法
计算分位值会导致肯定的误差 - 对这个
线性插值法
总是了解的不到位 -
在查看完代码之后明确了
代码剖析
- 代码地位:
D:workgo_workpkgmodgithub.comprometheusprometheus@v0.0.0-20201209205804-66f47e116e00promqlquantile.go
bucket 数据结构
- 其中
bucket
代表当时定义好的 bucket upperBound
代表这个 bucket 的上限值count
代表这个小于等于这个upperBound
的个数 / 次数workqueue_work_duration_seconds_bucket{name="crd_openapi_controller",le="10"} 65246
- 所以上述表达式含意为
workqueue_work_duration_seconds
小于10
秒的有65246
个
type bucket struct {upperBound float64 count float64}
type buckets []bucket
func (b buckets) Len() int { return len(b) }
func (b buckets) Swap(i, j int) {b[i], b[j] = b[j], b[i] }
func (b buckets) Less(i, j int) bool {return b[i].upperBound < b[j].upperBound }
外围计算函数
- 外围函数如下
func bucketQuantile(q float64, buckets buckets) float64 {if q < 0 { return math.Inf(-1) } if q > 1 {return math.Inf(+1) } sort.Sort(buckets) if !math.IsInf(buckets[len(buckets)-1].upperBound, +1) {return math.NaN() }
buckets = coalesceBuckets(buckets) ensureMonotonic(buckets)
if len(buckets) < 2 {return math.NaN() } observations := buckets[len(buckets)-1].count if observations == 0 {return math.NaN() } rank := q * observations b := sort.Search(len(buckets)-1, func(i int) bool {return buckets[i].count >= rank })
if b == len(buckets)-1 {return buckets[len(buckets)-2].upperBound } if b == 0 && buckets[0].upperBound <= 0 {return buckets[0].upperBound } var (bucketStart float64 bucketEnd = buckets[b].upperBound count = buckets[b].count ) if b > 0 {bucketStart = buckets[b-1].upperBound count -= buckets[b-1].count rank -= buckets[b-1].count } sql:=fmt.Sprintf("%v+(%v-%v)*(%v/%v)", bucketStart, bucketEnd, bucketStart, rank, count,
) log.Println(sql) return bucketStart + (bucketEnd-bucketStart)*(rank/count)}
- 咱们当初有这些数据,而后求 75 分位值
a := []bucket{{upperBound: 0.05, count: 199881}, {upperBound: 0.1, count: 212210}, {upperBound: 0.2, count: 215395}, {upperBound: 0.4, count: 319435}, {upperBound: 0.8, count: 419576}, {upperBound: 1.6, count: 469593}, {upperBound: math.Inf(1), count: 519593},}
q75 := bucketQuantile(0.75, a)
- 其计算逻辑为:依据记录总数和分位值求指标落在第几个 bucket 段
b
- 依据
b
失去起始 bucket 大小bucketStart
, 终止 bucket 大小bucketStart
,本 bucket 宽度,本 bucket 记录数 - 依据本段记录数和分位值算出指标分位数在本 bucket 排行
rank
- 最终的计算形式为
分位值 = 起始 bucket 大小 +(本 bucket 宽度)*(指标分位数在本 bucket 排行 / 本 bucket 记录数)
- 换老本例中:
q75=0.4+(0.8-0.4)*(70259.75/100141) = 0.6806432929569308
2021/02/02 19:08:55 记录总数 = 5195932021/02/02 19:08:55 指标落在第几个 bucket 段 = 4
2021/02/02 19:08:55 起始 bucket 大小 = 0.4
2021/02/02 19:08:55 终止 bucket 大小 = 0.8
2021/02/02 19:08:55 本 bucket 宽度 = 0.4
2021/02/02 19:08:55 本 bucket 记录数 = 100141
2021/02/02 19:08:55 指标分位数在本 bucket 排行 = 70259.75
2021/02/02 19:08:55 分位值 = 起始 bucket 大小 +(本 bucket 宽度)*(指标分位数在本 bucket 排行 / 本 bucket 记录数)
2021/02/02 19:08:55 0.4+(0.8-0.4)*(70259.75/100141) = 0.6806432929569308
那线性插值法的含意体现在哪里呢
- 就是这里
本 bucket 宽度 *(指标分位数在本 bucket 排行 / 本 bucket 记录数)
- 有个假设:样本数据这个指标 bucket 中依照均匀距离均匀分布
- 举例 100141 个样本在 0.4-0.8 bucket 中均匀分布
- 如果实在值散布凑近 0.4 一些,则计算出的值偏大
- 如果实在值散布凑近 0.8 一些,则计算出的值偏小
- 这就是线性插值法的含意
histogram 高基数问题
- 具体能够看我之前写的文章 prometheus 高基数问题和其解决方案
危害在哪里
- 一个高基数的查问会把存储打挂
- 一个 50w 基数查问 1 小时数据内存大略的耗费为 1G,再叠加 cpu 等耗费
为何会呈现
- label 乘积太多,比方 bucket 有 50 种,再叠加 4 个 10 种的业务标签,所以总基数为
50*10*10*10*10=50w
summary 流式聚合
一个 qps 为 1 万的 http 服务接口的分位值如何计算
- 假如以 1 秒为窗口拿到 1 万个申请的响应工夫,排序算分位值即可
- 然而 1 秒窗口期内,快的申请会多,慢的申请会少
- 原理剖析:说来惭愧,没看太明确,然而能够确定就是 hold 一段时间的点计算的
- 感兴趣的能够本人钻研下
D:workgo_workpkgmodgithub.comprometheusclient_golang@v1.9.0prometheussummary.go