强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Prometheus 完全指南 / 06 - PromQL 进阶

06 - PromQL 进阶

6.1 子查询(Subquery)

子查询允许在一个查询中嵌套另一个范围查询,实现复杂的多层时间分析。

语法

<instant_query>[<range>:<step>] [offset <duration>]
  • <range>:回溯的时间范围
  • <step>:采样步长(可选,默认使用全局 evaluation_interval)

示例

# 过去 1 天内,每小时的平均请求速率
rate(http_requests_total[5m])[1d:1h]

# 过去 1 天内,每 5 分钟的最大 CPU 使用率
max_over_time(
  (rate(node_cpu_seconds_total{mode!="idle"}[5m]))[1d:5m]
)

# 过去 1 小时,每分钟的 P99 延迟
histogram_quantile(0.99,
  rate(http_request_duration_seconds_bucket[5m])
)[1h:1m]

# 使用 offset 对比
max_over_time(node_load1[1h:1m]) / max_over_time(node_load1[1h:1m] offset 1d)

子查询注意事项

事项说明
内存消耗子查询返回大量数据点,注意内存
性能影响子查询会增加查询时间
step 省略省略 step 时使用全局 evaluation_interval
嵌套限制避免过多层嵌套

注意:子查询适合分析和调试,不适合用于告警规则(性能差)。告警规则应使用录制规则预聚合。


6.2 预测函数

Prometheus 提供了一些用于趋势分析和预测的函数。

predict_linear()

基于线性回归预测未来值。

# 语法
predict_linear(v range-vector, t scalar)

# 预测 4 小时后的磁盘空间(字节)
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[1h], 4*3600)

# 告警:磁盘将在 4 小时内用完
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[6h], 4*3600) < 0

# 预测 1 小时后的内存使用率
predict_linear(
  (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)[1h],
  3600
) > 0.95

工作原理

时间序列值 ──► 最小二乘法线性回归 ──► 斜率和截距 ──► 外推预测

示例数据(磁盘可用空间,过去1小时):
T=0h: 50GB, T=15m: 48GB, T=30m: 46GB, T=45m: 44GB, T=1h: 42GB

线性回归: 斜率 ≈ -8GB/h

预测4小时后: 42GB + (-8GB/h) × 4h = 10GB

holt_winters()

基于双重指数平滑(Holt-Winters)的预测。

# 语法
holt_winters(v range-vector, sf scalar, tf scalar)
# sf: 平滑因子 (0-1)
# tf: 趋势因子 (0-1)

# 预测下个时刻的值
holt_winters(http_requests_total[1h], 0.3, 0.1)

deriv()

计算时间序列的每秒导数(变化率),适用于 Gauge 类型。

# 磁盘空间的每秒变化率
deriv(node_filesystem_avail_bytes{mountpoint="/"}[1h])

# 内存变化率
deriv(node_memory_MemAvailable_bytes[30m])

6.3 高级聚合函数

topk / bottomk

# CPU 使用率最高的 5 台机器
topk(5,
  100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
)

# 内存使用率最低的 3 台机器
bottomk(3,
  (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
)

# 最慢的 10 个 API 端点
topk(10,
  histogram_quantile(0.99, sum by (path, le) (rate(http_request_duration_seconds_bucket[5m])))
)

count_values

按值计数,统计每个值出现的序列数。

# 统计各状态码的实例数量
count_values("status_code", http_requests_total)

# 统计各健康状态的实例数
count_values("health", up)

quantile

计算分位数(跨所有序列)。

# 所有实例 CPU 使用率的 P95
quantile(0.95,
  100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
)

# 所有实例的 P50 内存使用率
quantile(0.5,
  (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
)

group

将所有值设为 1 的聚合函数,用于检查某个时间序列是否存在。

# 检查是否有来自 job="api" 的时间序列
group by (job) (http_requests_total{job="api"})

6.4 高级内置函数

absent() 和 absent_over_time()

检测时间序列是否不存在。

# 如果 up{job="api"} 不存在,返回 1
absent(up{job="api"})

# 如果过去 5 分钟内没有数据,返回 1
absent_over_time(up{job="api"}[5m])

# 用于告警:实例丢失告警
absent(up{job="api"}) == 1

# 复合条件
absent(up{job="api"}) or (up{job="api"} == 0)

changes()

计算时间范围内值变化的次数。

# 过去 1 小时配置重载了多少次
changes(prometheus_config_last_reload_success_timestamp_seconds[1h])

# 过去 24 小时实例重启次数
changes(process_start_time_seconds[24h])

resets()

计算 Counter 重置的次数。

# 过去 1 小时 Counter 重置次数
resets(http_requests_total[1h])

# 重置通常意味着进程重启

delta() 和 idelta()

# Gauge 的变化量(过去 1 小时)
delta(node_memory_MemAvailable_bytes[1h])

# 最后两个数据点的差值
idelta(node_memory_MemAvailable_bytes[5m])

vector() 和 scalar()

# 将标量转为向量
vector(42)

# 将单序列向量转为标量
scalar(up{job="prometheus"})

time() 和 timestamp()

# 当前时间戳
time()

# 序列最后一次更新的时间戳
timestamp(up)

# 数据过期检测(最后更新超过 5 分钟)
(time() - timestamp(up)) > 300

6.5 复杂查询实战

业务场景:SLI/SLO 计算

# SLI: 可用性 = 成功请求 / 总请求
# 定义:状态码为 2xx 或 3xx 的请求为成功
sum(rate(http_requests_total{status=~"[23].."}[30d]))
/
sum(rate(http_requests_total[30d]))

# SLI: 延迟达标率(P99 < 500ms 的请求占比)
sum(rate(http_request_duration_seconds_bucket{le="0.5"}[30d]))
/
sum(rate(http_request_duration_seconds_count[30d]))

# SLO 告警:30天滚动窗口内错误预算消耗过快
# SLO = 99.9%,即 30 天内允许 43.2 分钟不可用
# 错误预算 = 1 - SLI
(
  1 - (
    sum(rate(http_requests_total{status=~"[23].."}[30d]))
    / sum(rate(http_requests_total[30d]))
  )
) > 0.001  # 超过 0.1% 错误率

业务场景:容量规划

# 预测未来 7 天的磁盘使用
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[7d], 7*24*3600) < 0

# 预测未来 30 天的存储增长
predict_linear(prometheus_tsdb_storage_blocks_bytes[30d], 30*24*3600)

# 当前 QPS 趋势
avg_over_time(rate(http_requests_total[5m])[1h:5m])

# QPS 峰值分析
max_over_time(rate(http_requests_total[5m])[24h])

业务场景:多集群对比

# 不同集群的请求速率
sum by (cluster, job) (rate(http_requests_total[5m]))

# 集群间的差异比较
# 假设有 cluster 标签
label_replace(
  sum by (cluster) (rate(http_requests_total[5m])),
  "cluster_short", "$1", "cluster", "(.*)-cluster"
)

业务场景:异常检测

# 与过去一周同期对比,偏差超过 2 倍
rate(http_requests_total[5m])
> 2 * rate(http_requests_total[5m] offset 1w)

# 与过去 1 小时平均值对比
rate(http_requests_total[5m])
> 2 * avg_over_time(rate(http_requests_total[5m])[1h])

# 基于 stddev 的异常检测
abs(
  rate(http_requests_total[5m])
  - avg_over_time(rate(http_requests_total[5m])[1h])
)
> 2 * stddev_over_time(rate(http_requests_total[5m])[1h])

6.6 查询性能优化

时间窗口选择

# ❌ 过大的时间窗口(慢查询)
rate(http_requests_total[24h])

# ✅ 使用录制规则预聚合
# 先在 recording rules 中计算 rate(http_requests_total[5m])
# 然后查询预聚合结果

标签过滤位置

# ❌ 先聚合再过滤(数据量大)
sum(rate(http_requests_total[5m])) > 100

# ✅ 先过滤再聚合(数据量小)
sum(rate(http_requests_total{job="api"}[5m])) > 100

避免子查询

# ❌ 子查询(慢)
max_over_time(rate(http_requests_total[5m])[24h:1m])

# ✅ 使用录制规则
# 预先计算 rate(http_requests_total[5m]),存为 recording rule
# 然后查询录制结果
max_over_time(http_requests_rate_5m[24h])

6.7 常见错误与陷阱

陷阱一:Counter 重置

# ❌ 直接使用 Counter 值(进程重启后值会重置)
http_requests_total

# ✅ 使用 rate() 自动处理重置
rate(http_requests_total[5m])

陷阱二:除零错误

# ❌ 可能除零
rate(http_requests_total{status="500"}[5m]) / rate(http_requests_total[5m])

# ✅ 使用 vector(0) 或 or 提供默认值
rate(http_requests_total{status="500"}[5m])
/
(rate(http_requests_total[5m]) > 0)

陷阱三:标签不匹配

# ❌ 两边标签不一致导致结果为空
sum by (job) (rate(http_requests_total[5m]))
/ sum by (instance) (rate(http_request_duration_seconds_count[5m]))

# ✅ 确保匹配标签一致
sum by (job) (rate(http_requests_total[5m]))
/ sum by (job) (rate(http_request_duration_seconds_count[5m]))

陷阱四:rate 窗口太小

# ❌ rate 窗口小于 2x scrape_interval(可能得到 0)
rate(http_requests_total[10s])  # 如果 scrape_interval=15s,这会失败

# ✅ rate 窗口 ≥ 4x scrape_interval
rate(http_requests_total[1m])  # 15s × 4 = 60s

6.8 本章小结

技术用途注意事项
子查询多层时间分析注意内存消耗
predict_linear容量预测假设线性增长
holt_winters趋势预测需要足够数据
absent()序列缺失检测配合告警使用
changes()事件计数只适用于 Gauge
标签操作label_replace/join正则性能开销

扩展阅读


上一章05 - PromQL 基础 下一章07 - 告警管理