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 - 告警管理