你好,我是你的老朋友,监控达人“Prometheus小能手”。今天咱们来聊聊PromQL的那些高级玩法,保证让你对PromQL的理解更上一层楼!
前言:PromQL,不仅仅是查询
对于咱们SRE工程师来说,Prometheus就像是战场上的“鹰眼”,帮我们洞察一切。而PromQL,则是我们与Prometheus沟通的“密语”。别以为PromQL只是简单的查询,它其实蕴藏着强大的数据分析和告警能力。掌握PromQL的高级用法,能让你在监控的道路上事半功倍。
在之前的学习中,你可能已经熟悉了PromQL的基础查询,比如指标选择器、范围向量、瞬时向量等等。但这些只是冰山一角。今天,咱们就来揭开PromQL的神秘面纱,看看它还有哪些“隐藏技能”。
核心技能一:聚合操作(Aggregation)—— 数据汇总的艺术
想象一下,你现在要统计所有服务的平均CPU使用率,或者计算某个接口的99th分位延迟,怎么办?这时候,聚合操作就派上用场了。
PromQL提供了丰富的聚合函数,可以对数据进行各种维度的汇总。常见的聚合函数包括:
- sum():求和
- avg():求平均值
- min():求最小值
- max():求最大值
- count():计数
- stddev():求标准差
- stdvar():求方差
- quantile(φ, expr):计算分位数(φ的取值范围是0到1)
- topk(k, expr):取样本值最大的k个
- bottomk(k, expr):取样本值最小的k个
聚合的维度:by 和 without
聚合操作通常需要指定一个或多个维度,PromQL中使用by和without子句来实现。
- by:表示按照指定的维度进行分组聚合。
- without:表示排除指定的维度,按照剩余的维度进行分组聚合。
举个例子:
# 计算所有服务的平均CPU使用率
avg(node_cpu_seconds_total{mode="idle"}) without (cpu)
# 按照instance和job维度计算CPU使用率的总和
sum(node_cpu_seconds_total{mode="idle"}) by (instance, job)
思考: 为什么要区分by和without?它们各自的适用场景是什么?
by 和 without 的设计,让我们可以更灵活地控制聚合的维度。by 适用于明确知道要按照哪些维度分组的情况,而without 适用于只关心排除某些维度,而对剩余维度不关心的情况。 在性能方面,without 通常比 by 更高效,因为它只需要排除少量维度。
进阶用法:多重聚合
有时候,我们需要进行多重聚合,比如先按照instance分组求和,再计算所有instance的平均值。PromQL支持这种嵌套的聚合操作。
# 先按照instance分组求和,再计算所有instance的平均CPU使用率
avg(sum(node_cpu_seconds_total{mode="idle"}) by (instance))
注意: 多重聚合的顺序很重要,不同的顺序可能会得到不同的结果。
核心技能二:子查询(Subquery)—— 时间的切片
子查询是PromQL 2.7版本引入的一个强大特性。它允许我们在一个查询中嵌套另一个查询,实现更复杂的数据分析。
子查询的基本语法是:<表达式> [时间范围]
其中,时间范围用方括号[]表示,可以是一个绝对时间范围,也可以是一个相对时间范围。
子查询的应用场景
- 计算过去一段时间内的平均值、最大值、最小值等。 - # 计算过去1小时内CPU使用率的平均值 avg_over_time(node_cpu_seconds_total{mode="idle"}[1h])
- 计算速率的变化率。 - # 计算过去5分钟内请求速率的变化率 rate(http_requests_total[5m]) - rate(http_requests_total[10m])
- 结合 - quantile_over_time计算分位数
 子查询和- quantile_over_time组合可以用于计算一段时间内指标的分位数值。- quantile_over_time(0.9, http_request_duration_seconds[1h])
- 更复杂的计算 
 假设你需要计算过去1小时内CPU使用率的平均值,并且只考虑CPU使用率大于0.5的时间点。 这时候可以结合子查询做更复杂的计算。- avg_over_time((node_cpu_seconds_total{mode="idle"} > 0.5)[1h:])
注意: 子查询的时间范围必须是方括号[]括起来,并且不能包含offset。
核心技能三:直方图(Histogram)—— 数据的分布
直方图是PromQL中一种特殊的指标类型,用于表示数据的分布情况。它可以帮助我们了解数据的集中趋势、离散程度等信息。
Prometheus的直方图是累积直方图(Cumulative Histogram)。累积直方图由一系列的bucket(桶)组成,每个bucket包含一个上限值和一个计数器。计数器的值表示小于等于该上限值的样本数量。
直方图的指标
直方图类型的指标通常包含以下三个子指标:
- <basename>_bucket{le="<upper inclusive bound>"}:表示小于等于上限值的样本数量。
- <basename>_sum:表示所有样本值的总和。
- <basename>_count:表示所有样本的总数。
例如,http_request_duration_seconds这个直方图指标,就包含了http_request_duration_seconds_bucket、http_request_duration_seconds_sum和http_request_duration_seconds_count三个子指标。
直方图的函数
PromQL提供了一些专门用于处理直方图的函数:
- histogram_quantile(φ, b):计算分位数(φ的取值范围是0到1,b是bucket)。
- histogram_sum(b):计算所有样本值的总和。
- histogram_count(b):计算所有样本的总数。
如何使用直方图计算分位数
# 计算http_request_duration_seconds的99th分位延迟
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))
注意: histogram_quantile函数计算出的分位数是近似值,精度取决于bucket的划分。
性能优化:让你的查询飞起来
PromQL的性能优化是一个永恒的话题。以下是一些常用的优化技巧:
- 合理使用标签(Label):标签是PromQL的灵魂,但过多的标签会增加查询的负担。尽量减少不必要的标签,只保留真正需要的维度。
- 避免使用高基数(High Cardinality)的标签:高基数标签指的是具有大量不同值的标签,比如用户ID、IP地址等。这类标签会导致Prometheus存储大量的时序数据,严重影响查询性能。如果必须使用高基数标签,可以考虑将其拆分成多个低基数标签。
- 使用记录规则(Recording Rules):对于一些常用的、复杂的查询,可以将其定义为记录规则。记录规则会预先计算结果并存储为新的时序数据,从而加快查询速度。
- 合理设置查询范围:查询范围越大,需要扫描的数据就越多,查询速度就越慢。尽量缩小查询范围,只查询必要的数据。
- 使用rate()函数时,注意范围向量的选择器时长至少是抓取间隔的4倍:这是为了避免“长尾效应”对速率计算的影响。
- 避免在rate()函数中使用sum(): 这样做通常是错误的,因为rate()函数本身已经包含了对时间窗口内样本的平均。
- 使用up指标检查目标是否存活:up指标是一个特殊的指标,用于表示目标的健康状态。可以利用up指标快速判断目标是否存活,避免不必要的查询。
告警实战:让Prometheus成为你的“哨兵”
PromQL不仅可以用于查询和分析数据,还可以用于定义告警规则。Prometheus的告警规则基于PromQL表达式,当表达式的值满足特定条件时,就会触发告警。
告警规则的组成
一个告警规则通常包含以下几个部分:
- alert:告警名称。
- expr:PromQL表达式,用于计算告警条件。
- for:持续时间,表示告警条件持续多长时间才触发告警。
- labels:附加的标签,用于标识告警的严重程度、类型等信息。
- annotations:附加的注释,用于描述告警的详细信息、处理建议等。
一个简单的告警规则示例
alert: HighRequestLatency
expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 1
for: 5m
labels:
  severity: critical
annotations:
  summary: "High request latency"
  description: "The 99th percentile request latency is above 1 second for 5 minutes."
这个告警规则表示:当http_request_duration_seconds的99th分位延迟超过1秒,并且持续5分钟以上,就会触发一个名为HighRequestLatency的告警,告警级别为critical。
总结:PromQL,监控的利器
今天,我们一起学习了PromQL的高级用法,包括聚合操作、子查询、直方图以及性能优化和告警实战。相信你对PromQL的理解又加深了一层。
PromQL是一个强大而灵活的工具,掌握它可以让你更好地理解和分析监控数据,及时发现和解决问题。希望今天的分享能对你有所帮助,让Prometheus成为你监控的得力助手!
如果你在使用PromQL的过程中遇到任何问题,或者有什么好的想法和建议,欢迎随时与我交流。我是“Prometheus小能手”,我们下期再见!
附录:PromQL常用函数一览表
| 函数 | 描述 | 适用类型 | 
|---|---|---|
| abs(v instant-vector) | 返回v中所有样本的绝对值 | 瞬时向量 | 
| ceil(v instant-vector) | 返回v中所有样本值向上取整 | 瞬时向量 | 
| floor(v instant-vector) | 返回v中所有样本值向下取整 | 瞬时向量 | 
| round(v instant-vector, to_nearest=1) | 返回v中所有样本值四舍五入到最近的整数,to_nearest参数可选,表示四舍五入的精度 | 瞬时向量 | 
| changes(v range-vector) | 返回v中每个时间序列在时间范围内的变化次数 | 范围向量 | 
| clamp_max(v instant-vector, max scalar) | 将v中所有样本值限制在max以下 | 瞬时向量 | 
| clamp_min(v instant-vector, min scalar) | 将v中所有样本值限制在min以上 | 瞬时向量 | 
| day_of_month(v=vector(time()) instant-vector) | 返回UTC时间中每个样本值所在月份的第几天(1-31) | 瞬时向量(可选) | 
| day_of_week(v=vector(time()) instant-vector) | 返回UTC时间中每个样本值所在周的第几天(0-6,0表示周日) | 瞬时向量(可选) | 
| days_in_month(v=vector(time()) instant-vector) | 返回UTC时间中每个样本值所在月份的天数(28-31) | 瞬时向量(可选) | 
| delta(v range-vector) | 计算范围向量v中每个时间序列的第一个值和最后一个值的差值 | 范围向量 | 
| deriv(v range-vector) | 计算范围向量v中每个时间序列的导数(基于线性回归) | 范围向量 | 
| exp(v instant-vector) | 计算v中所有样本值的指数(以e为底) | 瞬时向量 | 
| histogram_quantile(φ scalar, b instant-vector) | 计算直方图b中φ分位数的值(φ的取值范围是0到1) | 瞬时向量 | 
| holt_winters(v range-vector, sf scalar, tf scalar) | 计算范围向量v中每个时间序列的Holt-Winters预测值(sf是平滑因子,tf是趋势因子) | 范围向量 | 
| hour(v=vector(time()) instant-vector) | 返回UTC时间中每个样本值所在小时(0-23) | 瞬时向量(可选) | 
| idelta(v range-vector) | 计算范围向量v中每个时间序列的最后两个值的差值 | 范围向量 | 
| increase(v range-vector) | 计算范围向量v中每个时间序列的增长量(考虑计数器重置) | 范围向量 | 
| irate(v range-vector) | 计算范围向量v中每个时间序列的最后两个值的瞬时增长率 | 范围向量 | 
| label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string) | 对于v中的每个时间序列,使用regex匹配src_label的值,并将匹配到的部分替换为replacement,然后将结果写入dst_label中。如果未匹配到,则不进行替换。 | 瞬时向量 | 
| label_join(v instant-vector, dst_label string, separator string, src_label_1 string, src_label_2 string, ...) | 将v中每个时间序列的多个src_label的值使用separator连接起来,并将结果写入dst_label中。 | 瞬时向量 | 
| ln(v instant-vector) | 计算v中所有样本值的自然对数 | 瞬时向量 | 
| log10(v instant-vector) | 计算v中所有样本值的以10为底的对数 | 瞬时向量 | 
| log2(v instant-vector) | 计算v中所有样本值的以2为底的对数 | 瞬时向量 | 
| minute(v=vector(time()) instant-vector) | 返回UTC时间中每个样本值所在分钟(0-59) | 瞬时向量(可选) | 
| month(v=vector(time()) instant-vector) | 返回UTC时间中每个样本值所在月份(1-12) | 瞬时向量(可选) | 
| predict_linear(v range-vector, t scalar) | 基于范围向量v中每个时间序列的线性回归,预测t秒后的值 | 范围向量 | 
| rate(v range-vector) | 计算范围向量v中每个时间序列的平均每秒增长率(考虑计数器重置) | 范围向量 | 
| resets(v range-vector) | 返回v中每个时间序列在时间范围内的重置次数 | 范围向量 | 
| scalar(v instant-vector) | 如果v只有一个时间序列,则返回该时间序列的样本值作为标量;否则,返回NaN | 瞬时向量 | 
| sort(v instant-vector) | 对v中的时间序列按照样本值升序排序 | 瞬时向量 | 
| sort_desc(v instant-vector) | 对v中的时间序列按照样本值降序排序 | 瞬时向量 | 
| sqrt(v instant-vector) | 计算v中所有样本值的平方根 | 瞬时向量 | 
| time() | 返回当前UTC时间的时间戳(秒) | 无 | 
| timestamp(v instant-vector) | 返回v中每个样本的时间戳(秒) | 瞬时向量 | 
| vector(s scalar) | 将标量s转换为一个没有标签的向量 | 无 | 
| year(v=vector(time()) instant-vector) | 返回UTC时间中每个样本值所在年份 | 瞬时向量(可选) | 
注意: 这只是PromQL常用函数的一部分,更详细的函数列表请参考Prometheus官方文档。

