Appearance
PromQL
Prometheus 把指标存进 TSDB 之后,PromQL 负责从这些时序数据里筛选、计算、聚合和对比。Prometheus 的 Query 页面、Grafana 面板、告警规则,最终都落到 PromQL 表达式上。
写 PromQL 时最容易卡住的不是语法本身,是指标类型没认清(Counter 不能直接当当前值用)、标签维度没想好(聚合丢掉 instance 后没法定位机器)、时间范围没搞清楚(Instant Vector 和 Range Vector 什么时候用)。
时间序列:指标名 + 标签 = 唯一序列
TSDB 里每一条时间序列由"指标名 + 一组标签"唯一确定:
text
up{job="node", instance="192.168.10.11:9100", hostname="mon01"} 1
up{job="node", instance="192.168.10.12:9100", hostname="db01"} 1
up{job="node", instance="192.168.10.13:9100", hostname="cache01"} 1指标名都是 up,但 instance 和 hostname 不同,所以是三条不同的序列。给查询加了标签过滤条件后,就只在匹配到的序列里计算。
标签过滤写在 {} 里:
| 写法 | 含义 |
|---|---|
up | 查询所有 up 序列 |
up{job="node"} | 只查 job 为 node 的 |
up{job!="node"} | 排除 job 为 node 的 |
up{hostname=~"db.*"} | =~ 是正则匹配 |
up{hostname!~"db.*"} | !~ 是正则排除 |
Instant Vector 和 Range Vector
这两个概念贯穿所有 PromQL 查询。Instant Vector 是一个时间点上的一组序列及其值:
promql
up{job="node"} # "现在"这个时间点,三台机器的 up 值分别是多少Range Vector 是一段时间内的一组序列样本:
promql
node_cpu_seconds_total{job="node",mode="idle"}[5m]
# 最近 5 分钟内,每个时间点的 idle CPU 累计秒数[5m] 把 Instant Vector 变成 Range Vector。rate()、increase()、avg_over_time() 这些函数只能处理 Range Vector——它们需要一段时间内的多个样本才能计算。
| 表达式 | 类型 | 说明 |
|---|---|---|
up | Instant Vector | 当前查询时间点的值 |
up[5m] | Range Vector | 最近 5 分钟内的所有样本点 |
rate(http_requests_total[5m]) | Instant Vector | 输入 Range Vector,根据样本增长算出每秒速率,输出还是 Instant Vector |
Prometheus 页面的 Graph 模式按时间范围连续计算表达式,图上看到的曲线是很多个 Instant Vector 拼起来的;Table 模式只看当前时间点的值和标签。
Counter:算速率,不直接看当前值
Counter 只增不减(进程重启会归零)。请求总数、网卡累计字节数、CPU 累计运行秒数——都是 Counter。直接画 Counter 只会看到一条不断往上的线,排查流量和 CPU 时更想知道"每秒变化多少":
promql
rate(node_network_receive_bytes_total{job="node",device!~"lo|veth.*"}[5m])rate() 取 Range Vector 里的样本,算平均每秒增长速率。Counter 归零时(进程重启),rate() 内部会自动处理——看到当前值比上一个值小,就按重置计算增量,不会算出负速率。
rate() 和 increase() 的区别:rate() 是每秒速率,increase() 是一段时间内的总增量。Grafana 里画图常用 rate(),告警规则里有时用 increase() 判断"过去 N 分钟增加了多少"。
Gauge:直接查当前值
Gauge 可增可减,和温度计一样。当前内存、当前连接数、磁盘可用空间——直接查就行:
promql
node_memory_MemAvailable_bytes{job="node"}
mysql_global_status_threads_connectedGauge 也可以做 avg、max、min 聚合,看整体情况。
Histogram:从桶里算分位数
Histogram 用多个桶记录数据分布。比如 HTTP 请求耗时,不是只记一个平均值,而是统计 ≤0.1s 几个、≤0.5s 几个、≤1s 几个……从桶的分布里能算出 P95、P99:
promql
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))每个桶本身也是 Counter(只增不减),所以先用 rate() 转成速率,再调 histogram_quantile() 求分位。P95=200ms 和平均值=200ms 含义完全不一样——平均值 200ms 可能是"大部分请求 50ms,偶尔几个 5 秒"的结果,分位数能暴露这个长尾。
四种类型的查询方式总结:
| 类型 | 特点 | 常见写法 |
|---|---|---|
| Counter | 只增不减,重启归零 | rate()、increase() |
| Gauge | 可增可减 | 直接查,或 avg()、max() |
| Histogram | 分桶记录分布 | histogram_quantile() + rate() |
| Summary | 客户端侧算好分位 | 直接查,但跨实例无法重新聚合 |
常用主机查询
按 job 统计成功抓取数量:
promql
sum by (job) (up)CPU 使用率——用 100 减去 idle 占比。avg by (instance) 按机器聚合,避免每个 CPU 核心各出一条线:
promql
100 - (
avg by (instance) (
rate(node_cpu_seconds_total{job="node",mode="idle"}[5m])
) * 100
)内存使用率——MemAvailable 比 MemFree 更接近"还能分配多少内存"。Linux 会把空闲内存用作 page cache,MemFree 看着低但随时能被回收:
promql
100 * (1 - (
node_memory_MemAvailable_bytes{job="node"}
/
node_memory_MemTotal_bytes{job="node"}
))根分区使用率——fstype!~"tmpfs|overlay" 过滤虚拟文件系统。tmpfs 是内存文件系统,overlay 常见于容器,和真实磁盘容量无关:
promql
100 * (1 - (
node_filesystem_avail_bytes{job="node",mountpoint="/",fstype!~"tmpfs|overlay"}
/
node_filesystem_size_bytes{job="node",mountpoint="/",fstype!~"tmpfs|overlay"}
))聚合:by 决定保留什么标签
by 指定保留哪些标签,其余的被聚合掉。without 反过来——指定去掉哪些标签,其余保留:
promql
avg by (instance) (rate(node_cpu_seconds_total{job="node",mode!="idle"}[5m]))
# 结果按 instance 分组,每个 instance 一个值
count by (job) (up)
# 按 job 统计数量,instance/hostname 被聚掉了
up{job="node"} == 0
# 直接过滤出抓取失败的实例——没有聚合,保留完整标签用来定位聚合维度选择的影响:
| 聚合方式 | 适合 | 失去什么 |
|---|---|---|
sum by (job) | 看一类服务整体 | 无法定位具体哪台机器 |
avg by (instance) | 看每台机器的平均资源 | 丢失 CPU 核心维度 |
max by (mountpoint) | 看每台机器每个挂载点 | 告警条数可能翻倍 |
| 不聚合 | 排查原始序列 | 面板上可能太乱 |
告警和看板的聚合取舍不一样。看板可以保留更多维度方便排查;告警规则如果按 instance + mountpoint 出结果,每台机器每个挂载点都可能触一条告警——维度太细时考虑收窄聚合或配合 Alertmanager 做分组。
验证 PromQL 的入口
Prometheus Query 页面适合验证表达式本身:先确认 up{job="node"} 能返回三台主机,再逐步加函数和聚合。Grafana Explore 用同一条 PromQL 交叉确认数据源和查询结果。
| 平台 | 路径 | 用途 |
|---|---|---|
| Prometheus | Query(中文:查询) | 验证 PromQL 是否有结果 |
| Prometheus | Status → Target health | 看 target 抓取状态 |
| Grafana | Explore(中文:探索) | 用 Grafana 数据源执行临时查询 |
| Grafana | Dashboards → 面板菜单 → Edit | 检查面板的 PromQL、变量和单位 |
Grafana 查不到数据、Prometheus Query 能查到,问题在 Grafana 数据源、时间范围或面板变量。两边都查不到,回到 Targets 页面和指标名确认。
常见查询问题
| 现象 | 常见原因 | 先看什么 |
|---|---|---|
| 查询无结果 | 指标名写错、标签不匹配、时间范围没数据 | 先查指标名,再逐个加标签条件 |
| 曲线中间断了 | 某段时间 target 抓取失败、Prometheus 重启 | 查 up 指标和 Targets 页面 |
| CPU 数值异常高或低 | Counter 直接使用没加 rate(),或聚合维度错了 | 用 rate()、明确 by(instance) |
| 磁盘面板出现很多虚拟挂载点 | 没过滤 tmpfs、overlay | `fstype!~"tmpfs |
| 多台机器的数据混成一条线 | 聚合丢了 instance | 检查 by 和 without |
| Counter 曲线突然掉到底再涨 | 进程重启归零 | rate() 自动处理,但突降痕迹还在 |
写复杂表达式时,从最简开始一层一层加:原始指标能不能查到 → 加一个标签后还能不能查到 → 套函数后结果对不对 → 聚合后标签维度是不是还够定位。每一步都能确认结果,出了问题也容易知道是哪一步不对。