Prometheus 受启发于 Google 的 Borgmon 监控系统,从 2012 年开始由前 Google 工程师在 Soundcloud 以开源软件的形式进行研发,并且于 2015 年对外发布早期版本。2016年5月继 Kubernetes 之后成为第二个正式加入 CNCF 基金会的项目,2018年8月9日,云原生计算基金会(CNCF)宣布开放源代码监控工具 Prometheus 已从孵化状态进入毕业状态,标志着 Prometheus 已经具备稳定性和成熟度,而且得到了市场的认可,已经成为了云原生中指标监控的事实标准。目前在 GitHub 已有超过 53.1k star。
然而在实际的使用场景中,我们会经常发现 Prometheus 存在指标值不准的“怪现象”,这么完美的主流监控方案,为何会存在这样的问题,究竟是 Bug 还是 Feature?本文将通过典型案例详细拆解、探讨其背后的设计原理。
有一天,你打算试用 Prometheus,监控你的业务系统。
你来到腾讯云,仅需几次点击,指标便从四面八方来,汇聚成 Grafana 上的优雅曲线。
“不愧是云原生监控一哥,容器集成十分顺滑!交给云托管则更省心,连云产品也一键监控了。”
手握丰富的 Grafana 大盘,和适时的告警通知,你深感满意。
然而,没过多久,你发现不对劲:
——“32 核的 CPU,监控出来是 44.3 核?”
——“P99 百分位的值,竟比最大值还高?”
——“用不同时间范围计算 rate,出来的曲线天壤之别?”
——……
你搜索全网、询问 ChatGPT 老师、向腾讯云提工单,得到的答复干脆而统一:“Prometheus 的指标值,并非 100% 准确”。
你刨根问底,却碰上“外推”、“插值”、“窗口对齐”……等晦涩的概念。于是,你跳过解题步骤,直接背诵了答案:“Prometheus 的指标值,并非 100% 准确”。
然而,一些灵魂拷问在你脑中浮现:
——既然大家都知道它不准,为何人人还都安利它?
——现在我也知道它不准了,还值得继续用下去吗?
以上内容,纯属虚构;如有雷同,那必然是关于 Prometheus 的“谜团”太多,而“解谜”太少。
而本文正打算以一种不烧脑、能秒懂的方式,分析一些最常见的案例。
长话短说,结论先行:Prometheus 指标值不准的“怪现象”,其实是在下面的“不可能三角”中,做出了取舍——为保全效率和可用性,舍弃了精度:
为何精度会被 Prometheus 舍弃?
归根结底,在可观测的世界里,metrics(指标)、log(日志)、trace(调用链) 三足鼎立。
而 Prometheus 所在的 metrics(指标) 领域,其目标是诊断整体健康状况,其手段通常是对原始数据先采样、再聚合,利用有限的信息,分析变化趋势;而并非像 log(日志)那样,翔实精确、事无巨细地,记录每一桩事件、每一条原始数据。
我们不妨用心脏监测来做类比:
如此看来,运动手表监测心率虽不精确,但胜在方便高效:不用跑到医院,就能 24 小时持续监控,还能自行设置告警阈值。在日常观测健康趋势方面,已然十分够用了。
除了 metrics 领域自身的特性,Prometheus 毕竟处在一个条件有限的真实世界,它还要随时面临以下困难:
Prometheus 需要在上述限制下,交出 not perfect、但是 good enough 的指标。于是就有了下述设计:
接下来,让我们观察几种最常见的案例,代入 Prometheus 的第一视角,体会它是如何在条件有限中,做出抉择的。
3.1 失真的 rate/increase
在使用 rate 或者 increase 观测 counter 类型的指标增量时,经常碰到以下问题:
而一种最常见的原因,就是线性外推(linear extrapolation)。
简单粗暴解释:rate/increase[时间范围] 在计算该时间范围内的增量时,第一步要拿到该时间范围边界上(开始时刻和结束时刻)的样本点,相减得到差值。
然而事与愿违的是:在当前时间范围的边界,并不一定那么凑巧地有样本点存在。
此时 Prometheus 的选择是:naive 地假设所有样本点在该时间范围内是均匀分布的,然后按照这个均匀分布的线性规律,“脑补”估算出边界上的采样点。
那么,既然是 “脑补”,“补”出来一些不准确的值,也就不足为奇了。
假设有一个 counter 类型的指标 errors_total ,用于监控业务系统报错的次数。Prometheus 以 15 秒的间隔采样,采集到了如下样本:
现在需要计算一分钟之内,errors_total 值的增量,也即 increase(errors_total[1m])。(此处为方便起见,仅以 increase 为例。而 rate 本质上是一样的,只是将 increase 在 [时间范围] 内的总增量除以 [时间范围] 的秒数,得到了速率/按秒增量。例如本例中 rate 值就是 increase 值除以 60 秒)。
要计算 [1m] 的时间范围/取样窗口内的 increase,在最理想的情况下,Prometheus 根本不想关心这个窗口内的其他数据,而只需从窗口左边界取第一个点,右边界取最后一个点,相减即可:
然而在真实的世界中,[1m] 窗口的左右边界却很少能精准“踩中”样本点,而是像下图这样:
那么问题来了:这 1 分钟的增量该怎么算呢?
Prometheus 选择了一种简易的线性外推算法:取窗口覆盖范围内的第一个点和最后一个点,计算斜率,并按照该斜率将直线延伸至窗口边界,无中生有地“脑补”出虚拟的两个“样本点”,即可相减计算 increase 了:
如上所示,用绿圈圈所代表的“虚拟样本”相减,得到的 increase 1.3 不仅是个小数,还比实际值偏大,也就不足为奇了。
3.2 离谱的 histogram
每当采集到的样本与 Prometheus 八字不合,P99 往往好似在告诉我们:“在全人类当中,99% 的人月收入少于一万亿。”——没毛病,但也大可不必。
此处就不得不提一个真实历史事件了:我们团队除了有腾讯云 Prometheus,还有个宝藏产品叫 PTS 云压测,能以海量并发向服务端发起请求,来观测服务端在压力下的响应状况。
比如,在压测出来的报告里,与响应时间相关的图表长这样:
可以看到,PTS 搜集了响应时间的平均值、P50、P90、P95——但就是没有 P99。
其实,最早的时候是有的——毕竟,谁不想用 P99 来做 SLA 啊。
但是,云压测背后的指标存取,还是用的 Prometheus。
于是,在 PTS 还拥有 P99 的那些年,我们三番五次、屡屡破防,最终忍痛拿掉了 P99:
P99 是一个统计术语,代表着第99百分位数(99th percentile)。在性能监控和服务质量评估中,P99 常用来衡量响应时间或延迟的指标。具体来说,P99 的含义是在所有测量值中,有 99% 的数据点小于或等于这个值,而只有 1% 的数据点大于这个值。例如,如果一个网络服务的响应时间的 P99 是 200 毫秒,这意味着在所有的请求中,99% 的请求的响应时间都不会超过 200 毫秒,只有 1% 的请求的响应时间会超过这个数值。这是一个衡量系统在高负载下性能的重要指标,因为它可以告诉你绝大多数用户的体验如何。 |
---|
简单理解 P99 是怎么得来的:把样本按值的大小依序排队,队伍里第 99% 个样本的值,就是 P99。
那么 Prometheus 在用 histogram 计算 P99 的时候,是否要保存全部哪怕一亿个请求样本的耗时值,才能知道第 99% 的请求所用的时间呢?
显然这不是 Prometheus 的风格。Prometheus 的风格是:宁愿“脑补”,也不愿低效。
于是,跟上面 rate/increase 类似:先从茫茫多的原始数据中采样出样本点,放到各个 bucket(桶)里;然后 naive 地假设所有样本是均匀分布的,据此做线性插值,“无中生有”出所需的“样本点”。
让我们看一个简单案例,模拟每秒产生一个新的 HTTP 请求耗时的观察值,然后计算其 P99。
下面程序为了埋点生成 http_response_time_seconds 这一 histogram 指标,每秒钟暴露一个观察值:
package main
import (
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
httpDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "http_response_time_seconds",
Help: "HTTP response time distribution",
Buckets: []float64{0.1, 0.5, 100}, // 划分四个桶: <= 0.1、<=0.5、<=100、<=正无穷
})
)
func init() {
prometheus.MustRegister(httpDuration)
}
func main() {
go func() {
for {
// 每秒添加一个新的观察值,是个随机数
// 其值大小有 50% 概率落在 [0.1, 0.5);50% 概率落在 [0.5, 1)
if rand.Float64() < 0.5 {
httpDuration.Observe(rand.Float64()*0.4 + 0.1)
} else {
httpDuration.Observe(rand.Float64()*0.5 + 0.5)
}
time.Sleep(time.Second)
}
}()
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}
为了卡 Prometheus 的 feature(bug),我们这里划分桶的时候,是相当 naive、相当不合理的:划分四个桶: <= 0.1、<=0.5、<=100、<=正无穷。(对一群不超过 1 的针尖大小的样本值,特地划分一个 0.5 ~ 100 这样宽如黄浦江的 bucket 段,笔者也真是没安好心……)
histogram 的 http_response_time_seconds_bucket 指标是 counter 型,会统计采样到的观察值的样本,落在各个桶内的数量。
还记得我们代码逻辑把一半数值落在 [0.1, 0.5),一半在 [0.5, 1.0) 吗?程序运行一段时间后,将所有样本按值的大小依序排开,观察它们在各个桶内的分布,大致示意如下:
好了,现在要开始计算 P99 histogram_quantile(0.99, rate(http_response_time_seconds_bucket[5m])) 了。
假设现在总共采集到 100 个样本,其中:
将样本值从小到大排列,落在 0.1~0.5 bucket 段里的,我们叫它第 1号 ~ 50 号样本;落在 0.5~100 bucket 段里的,我们叫它第 51 号 ~ 第 100号样本。
P99 的计算逻辑如下:
最终计算分位值公式时,问题就来了。
Prometheus 只知道:
然而,它并不知道:
again,Prometheus “脑补” bucket 段内的全额差值,被均匀、线性地,分到了所有样本头上。
因为这样就又可以用线性插值的方式,来计算分位值了:
所求分位值 = bucket 段左边界值 + (bucket 段右边界值 - bucket 段左边界值) * (目标样本在本 bucket 段的排行 / 本 bucket 段的样本总数)
在我们的例子中,也即:P99 = 0.5 + (100 - 0.5) * (49/50) = 98.01
如图所示:上面的实心绿点代表一群值不超过 1 的真实样本,而由于桶的划分非常不合理,导致 Prometheus “脑补”出下面那群荧光绿圈,其分布与实际情况风马牛不相及,最终估算出的 P99 值高达 98,也就不足为奇了。
3.3 薛定谔的 range
当我们选择 rate 的 range 时,我们在选择什么?
仍以上述 rate(errors_total[时间范围]) 为例,若我们分别选时间范围 [30s]、[1m]、[5m],看一眼三者的 Grafana 图表,这不能说一模一样,只能说是毫不相关:随着时间范围扩大,主打一个逐渐平滑、失去尖峰……
有一说一,rate 不就是速率,速率不就是每秒增量吗?为啥时间范围窗口不同,差异如此之大?区别在于它们计算平均速率的时间窗口不同:
上面这段废话其实大有深意。
假设我们系统的错误数长期为0,而在某时刻暴增100(如下图日志所示)。
那么上述三种时间范围窗口,意味着将这 100 均分到 30秒,还是 60 秒,还是300秒;那么答案也显而易见:分母越大,按秒平均后的增量则越平滑。
而这就是上述三个 Grafana 曲线随 rate 窗口而峰值和形态大变的原因:
关于 rate duration 的选择,并没有一成不变的规则,它并不是越小越好。
选择较小的时间范围可以让你更快地发现问题,但也可能会让你的图表出现很多噪音,特别是在高变化的指标上。
相反,较大的时间范围可以提供更平滑的数据视图,但可能会延迟发现问题。
所以,在选择合适的时间范围时,应考虑以下因素:
最终,选择合适的时间范围需根据具体情况,进行实验和调整,不断优化这个参数。
以上列举的最典型案例,旨在解释为何 Prometheus 会出现指标值不准的“怪现象”,以及探究它背后的设计原理。
为了言简意赅、去粗存精地解说,上述论述仍然处于最核心、但也经过了简化的场景。
而 Prometheus 在实际使用中的情形,是影响因素更多、也更为复杂的。聊举几例:
如此种种,挂一漏万,难以尽述。
总而言之,本文聚焦在最常见、最核心的场景,解析为何 Prometheus 的值不准——而它真的是一个 feature,不是一个 bug。
腾讯云可观测平台的 Prometheus 还可以结合可观测的告警能力,大大减少了开发及运维成本。更多详情欢迎大家微信关注腾讯云可观测公众号,不仅可以限时免费体验产品,还能加入交流群与大佬一起探讨技术。
而围绕着 Prometheus 宇宙,当然有更复杂、更深入的问题亟待探讨。
Prometheus 一直在全面升级,欲知后事如何,欢迎强势关注腾讯云可观测团队,让我们一起 stay hungry, stay foolish;持续发掘、持续求索。
-End-
原创作者|雷畅