PromQL 运算符
上一章我们学会了「把想要的序列挑出来」。这一章解决下一个问题:怎么把两组序列组合起来做运算。
错误率 = 错误数 ÷ 总数。内存使用率 = 已用 ÷ 总量。这些查询都需要让两个向量「配对」相除。听起来简单,但 PromQL 的运算规则和普通编程语言完全不同——它运算的对象不是两个数字,而是两组带标签的序列。
理解「向量匹配」是从 PromQL 入门到熟练的分水岭。
1. 运算符全景
| 分类 | 运算符 | 作用对象 |
|---|---|---|
| 算术 | + - * / % ^ | 标量↔标量、向量↔标量、向量↔向量 |
| 比较 | == != > < >= <= | 同上,默认「过滤」,加 bool 变「求值」 |
| 逻辑/集合 | and or unless | 只能向量↔向量 |
| 聚合 | sum avg max min count 等 | 向量(下一章细讲,本章会用到) |
优先级从高到低(同级从左到右,^ 例外是右结合):
1. ^
2. * / % atan2
3. + -
4. == != > < >= <=
5. and unless
6. or不确定优先级时,加括号。PromQL 的括号是免费的,可读性收益巨大。
2. 算术运算符
2.1 向量 ↔ 标量:最简单的情况
标量会作用到向量的每一条序列上:
# 字节转 GB
node_memory_MemAvailable_bytes / 1024 / 1024 / 1024
# 秒转毫秒
rate(http_request_duration_seconds_sum[5m]) * 1000
# 比率转百分比
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
# 取模:找出奇数编号的分片
kafka_partition_id % 2输入: node_memory_MemAvailable_bytes{instance="a", job="node"} 8589934592
表达式: node_memory_MemAvailable_bytes / 1024 / 1024 / 1024
输出: {instance="a", job="node"} 8注意输出丢掉了指标名(__name__ 被删除),但保留了所有其他标签。这是 PromQL 的普遍规则:任何运算都会丢弃指标名,因为运算结果已经不是原来那个指标了。
2.2 向量 ↔ 向量:标签必须完全一致
这是关键规则:
两个向量做运算时,只有「标签集完全相同」的序列才会配对;配不上的序列直接从结果中消失。
看一个成功的例子:
左侧: node_memory_MemAvailable_bytes{instance="a", job="node"} 8589934592
node_memory_MemAvailable_bytes{instance="b", job="node"} 4294967296
右侧: node_memory_MemTotal_bytes{instance="a", job="node"} 17179869184
node_memory_MemTotal_bytes{instance="b", job="node"} 17179869184
表达式: node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes
结果: {instance="a", job="node"} 0.5
{instance="b", job="node"} 0.25两边除了指标名之外标签完全一致,所以 a 配 a、b 配 b,各自相除。
再看一个失败的例子:
左侧: http_requests_total{job="api", status="500", method="GET"} 47
右侧: http_requests_total{job="api"} 98260
表达式: 左 / 右
结果: 空!因为左侧多了 status 和 method 两个标签,右侧没有,标签集不相同 → 无法配对 → 返回空。
这是新手最常遇到的「查询返回空」的原因。 解决办法有两个方向:用 sum 抹平标签差异,或者用 on / ignoring 显式指定匹配规则(见第 5 节)。
2.3 实战:几个常用的算术查询
# 内存使用率(%)
100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)
# 磁盘使用率(%)
100 * (
1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}
/ node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}
)
# CPU 使用率(%):100 减去 idle 的占比
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 平均请求耗时(毫秒)
1000 *
rate(http_request_duration_seconds_sum[5m])
/ rate(http_request_duration_seconds_count[5m])
# 每个请求平均消耗多少 CPU 秒
rate(process_cpu_seconds_total[5m]) / rate(http_requests_total[5m])最后一个查询很有意思——它把两个完全不同的指标结合起来,得到一个「单位成本」指标。当代码引入性能退化时,这个数字会先于 CPU 使用率发出信号(因为 CPU 使用率还受流量影响,而单位成本不受)。
3. 比较运算符
3.1 默认行为是「过滤」,不是「求值」
这是 PromQL 和普通编程语言最不一样的地方:
up == 0在 Python 里 x == 0 返回 True 或 False。在 PromQL 里,它返回的是:
输入: up{job="api", instance="a"} 1
up{job="api", instance="b"} 0
up{job="web", instance="c"} 1
表达式: up == 0
输出: up{job="api", instance="b"} 0 ← 只剩满足条件的序列,值保持原样比较运算符是一个过滤器:满足条件的序列原样保留(值不变、标签不变,包括指标名也保留),不满足的直接丢弃。
这个设计非常适合告警——告警规则的语义就是「表达式有结果就告警,没结果就不告警」:
- alert: InstanceDown
expr: up == 0 # 有序列返回 = 有实例挂了 = 触发告警
for: 5m# 更多过滤示例
node_load1 > 4 # 负载超过 4 的机器
rate(http_requests_total[5m]) > 100 # QPS 超过 100 的序列
node_filesystem_avail_bytes < 10 * 1024^3 # 剩余空间不足 10GB
kafka_consumergroup_lag > 10000 # 堆积超过 1 万条的分区3.2 bool 修饰符:变成 0/1
加上 bool 关键字,语义就变成了「求值」——不过滤,把每条序列的值换成 0 或 1:
输入: up{instance="a"} 1
up{instance="b"} 0
up == 0 → {instance="b"} 0 (过滤,1 条)
up == bool 0 → {instance="a"} 0, {instance="b"} 1 (求值,2 条)注意 bool 版本还会删除指标名(因为结果已经不是 up 了)。
bool 的典型用途一:算比例。
# 有多少比例的实例是健康的
avg(up == bool 1)
# 有多少比例的请求耗时超过 1 秒(配合 Summary 时有用)
avg(rpc_duration_seconds{quantile="0.5"} > bool 1)bool 的典型用途二:画一条「状态线」。
不加 bool 的话,条件不满足时序列直接消失,Grafana 上会出现断线。加了 bool 就永远有值(0 或 1),能画出完整的状态阶梯图。
bool 的典型用途三:组合多个条件打分。
# 一个简单的健康评分:三个条件各占 1 分
(up == bool 1)
+ (node_load1 < bool 4)
+ (node_memory_MemAvailable_bytes > bool 1024^3)2 > 1 # ❌ 报错:comparisons between scalars must use BOOL modifier
2 > bool 1 # ✅ 返回标量 1原因是「过滤」对标量没有意义——一个标量要么留下要么消失,而 PromQL 的表达式必须有确定的返回类型。所以标量比较强制要求 bool。
3.3 向量之间的比较
两个向量比较时,同样遵循「标签必须一致才配对」的规则:
# 找出「可用内存 < 总内存的 10%」的实例
node_memory_MemAvailable_bytes < node_memory_MemTotal_bytes * 0.1结果保留的是左侧的序列(值和标签都来自左侧)。
# 找出当前 QPS 低于 1 小时前一半的服务(流量骤降)
sum by (job) (rate(http_requests_total[5m]))
< 0.5 * sum by (job) (rate(http_requests_total[5m] offset 1h))3.4 链式比较的陷阱
# ❌ 想表达「1 < x < 5」,但结果不对
1 < node_load1 < 5PromQL 从左到右求值:先算 1 < node_load1——注意左边是标量、右边是向量,这在语法上合法但语义混乱(结果保留右侧向量中大于 1 的序列)。然后再和 5 比较。
正确写法是用 and 连接两个独立条件:
node_load1 > 1 and node_load1 < 54. 逻辑与集合运算符
三个运算符:and(交集)、or(并集)、unless(差集)。它们只能用于向量与向量之间,且只看标签,不看值。
4.1 and:交集
返回:左侧向量中,「标签集在右侧也存在」的那些序列。
值和标签都来自左侧。左侧: node_load1{instance="a"} 5.2
node_load1{instance="b"} 0.3
node_load1{instance="c"} 8.1
右侧: node_memory_low{instance="a"} 1
node_memory_low{instance="c"} 1
node_load1 and node_memory_low
→ {instance="a"} 5.2
{instance="c"} 8.1最重要的用途:给告警加「附加条件」。
# 磁盘使用率 > 85% 且 剩余空间 < 50GB(避免大盘误报)
(100 * (1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) > 85)
and
(node_filesystem_avail_bytes < 50 * 1024^3)# 错误率 > 5% 且 QPS > 10(避免低流量误报)
(
sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
/ sum by (job) (rate(http_requests_total[5m]))
) > 0.05
and
sum by (job) (rate(http_requests_total[5m])) > 10这就是上一章反复强调的「比值型告警必须配绝对量下限」的实现方式。
4.2 or:并集
返回:左侧的全部序列 + 右侧中「标签集在左侧不存在」的序列。
左侧优先。左侧: metric_a{instance="a"} 1
metric_a{instance="b"} 2
右侧: metric_b{instance="b"} 99 ← 标签和左侧的 b 相同,被忽略
metric_b{instance="c"} 3 ← 左侧没有 c,被纳入
metric_a or metric_b
→ {instance="a"} 1
{instance="b"} 2 ← 来自左侧,右侧的 99 被丢弃
{instance="c"} 3 ← 来自右侧用途一:兜底默认值。
# 如果 my_metric 没有数据,返回 0 而不是空
my_metric or vector(0)这在 Grafana 单值面板上特别有用——没有数据时显示「No data」很难看,显示 0 更友好。
用途二:合并多个来源的同类指标。
# 新旧两套埋点并存的过渡期
sum(rate(http_requests_total[5m])) or sum(rate(legacy_http_requests_total[5m]))用途三:让告警在指标消失时也能触发。
# 服务挂了(up == 0)或者根本没有 up 指标(目标被删了)
up == 0 or absent(up{job="critical-api"})4.3 unless:差集
返回:左侧向量中,「标签集在右侧不存在」的那些序列。左侧: node_load1{instance="a"} 5.2
node_load1{instance="b"} 0.3
node_load1{instance="c"} 8.1
右侧: maintenance_mode{instance="c"} 1
node_load1 > 4 unless maintenance_mode
→ {instance="a"} 5.2 ← c 在维护中,被排除核心用途:排除例外。
# 磁盘告警,但排除正在维护的机器
(node_filesystem_avail_bytes / node_filesystem_size_bytes < 0.1)
unless on(instance)
node_maintenance_mode == 1# Pod 不健康告警,但排除正在滚动更新的 Deployment
kube_pod_status_ready{condition="false"} == 1
unless on(namespace, deployment)
kube_deployment_status_condition{condition="Progressing", status="true"} == 1比起在 Alertmanager 里配 silence,用 unless 在查询层就排除掉,有几个好处:
- 静默状态本身是一个指标,可以被查询、被审计、被展示在大盘上
- 可以做细粒度控制(按 instance、按 namespace)
- 静默生效和失效都是自动的,不会忘记取消
实现方式:用一个 Pushgateway 指标或者一个简单的 exporter 暴露维护状态:
node_maintenance_mode{instance="10.0.1.5:9100", reason="kernel_upgrade"} 1然后所有相关告警都加上 unless on(instance) node_maintenance_mode == 1。
4.4 三者对比
左侧 L: {a} {b} {c} 右侧 R: {b} {c} {d}
L and R → {b} {c} 交集(值取左侧)
L or R → {a} {b} {c} {d} 并集(重叠部分取左侧)
L unless R → {a} 差集用集合论的语言:and 是 ∩,or 是 ∪,unless 是 L − R。
5. 向量匹配:on / ignoring / group_left / group_right
现在进入本章的核心难点。
5.1 问题:标签不一致怎么办
回到第 2.2 节那个失败的例子:
# ❌ 返回空
sum by (job, status) (rate(http_requests_total{status=~"5.."}[5m]))
/ sum by (job) (rate(http_requests_total[5m]))左侧序列带 job 和 status 两个标签,右侧只带 job。标签集不同 → 配不上 → 空。
PromQL 提供了两个关键字来解决这个问题:
| 关键字 | 含义 |
|---|---|
on(l1, l2) | 只用列出的这些标签做匹配,其余标签忽略 |
ignoring(l1, l2) | 匹配时忽略列出的这些标签,其余标签都要一致 |
修正上面的查询:
# ✅ 只用 job 做匹配
sum by (job, status) (rate(http_requests_total{status=~"5.."}[5m]))
/ on(job)
sum by (job) (rate(http_requests_total[5m]))或者等价地:
# ✅ 忽略 status 标签
sum by (job, status) (rate(http_requests_total{status=~"5.."}[5m]))
/ ignoring(status)
sum by (job) (rate(http_requests_total[5m]))on 还是 ignoring? 经验:
- 两边标签差异很大、只有少数几个共同标签 → 用
on(明确列出用哪些配对) - 两边标签基本一致、只差一两个 → 用
ignoring(明确列出忽略哪些)
on 更常用,因为它的行为不会因为新增标签而意外改变。
5.2 三种基数关系
PromQL 的向量匹配有三种「基数」(cardinality),这是理解 group_left 的前提:
一对一 (one-to-one):
左侧每条序列 ↔ 右侧恰好一条序列
→ 默认行为,不需要任何额外关键字
多对一 (many-to-one):
左侧多条序列 → 右侧同一条序列
→ 需要 group_left
一对多 (one-to-many):
左侧一条序列 → 右侧多条序列
→ 需要 group_right5.3 一对一:默认行为
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes每个 instance 的可用内存对应它自己的总内存,一一对应。这是最常见的情况,什么都不用写。
如果一对一匹配中出现了「一个左侧序列匹配到多个右侧序列」,Prometheus 会报错:
Error: found duplicate series for the match group {job="api"} on the right hand-side
of the operation: [...]; many-to-many matching not allowed:
matching labels must be unique on one side看到这个报错,说明你需要的是多对一(或一对多),要加 group_left / group_right。
5.4 多对一:group_left
场景:一个「细粒度」向量除以一个「粗粒度」向量。
左侧(细): 每个 (job, status) 一条序列
{job="api", status="500"} 3.2
{job="api", status="502"} 0.8
{job="api", status="503"} 0.1
右侧(粗): 每个 job 一条序列
{job="api"} 120.5左侧 3 条序列都要除以右侧那 1 条 → 多对一 → 用 group_left:
sum by (job, status) (rate(http_requests_total{status=~"5.."}[5m]))
/ on(job) group_left
sum by (job) (rate(http_requests_total[5m]))结果:
{job="api", status="500"} 0.0266
{job="api", status="502"} 0.0066
{job="api", status="503"} 0.0008记忆方法:group_left 中的 "left" 指的是「多的那一侧在左边」。
group_left → 左侧是「多」,右侧是「一」 左 N : 1 右
group_right → 左侧是「一」,右侧是「多」 左 1 : N 右5.5 group_left 的第二个能力:把右侧的标签「贴」过来
group_left 后面可以跟一个标签列表,表示从右侧向量额外复制这些标签到结果里:
group_left(label1, label2)这是 PromQL 里极其实用的一个技巧,叫做**「info 指标关联」**。
场景:给指标补充元数据。
假设你有一个 info 指标记录了每个实例的版本、机房、负责团队:
myapp_build_info{instance="10.0.1.5:8080", version="2.3.1", region="bj", team="order"} 1
myapp_build_info{instance="10.0.1.6:8080", version="2.3.0", region="sh", team="order"} 1而 QPS 指标上只有 instance:
http_requests_total{instance="10.0.1.5:8080", job="api"} ...想按版本看 QPS,就用 group_left 把 version 标签贴过来:
sum by (version) (
sum by (instance) (rate(http_requests_total[5m]))
* on(instance) group_left(version)
myapp_build_info
)逐层解释:
1. sum by (instance) (rate(...)) 每个实例的 QPS
2. * on(instance) group_left(version) 用 instance 配对,从右侧带上 version 标签
(乘以 1 —— info 指标的值恒为 1,所以数值不变,只是为了「借」标签)
3. sum by (version) (...) 按版本聚合结果:
{version="2.3.1"} 85.2
{version="2.3.0"} 42.7info 指标的值恒为 1,所以 x * 1 = x,数值完全不变。这个乘法的唯一目的是触发一次向量匹配,从而把右侧的标签复制过来。
这是 PromQL 社区的一个惯用法(idiom)。看起来别扭,但因为 PromQL 没有 SQL 那样的 JOIN 语法,这就是标准做法。
同样的技巧可以用在很多地方:
# 按 Kubernetes Deployment 看 Pod 的 CPU 使用
sum by (deployment) (
rate(container_cpu_usage_seconds_total[5m])
* on(pod) group_left(deployment) kube_pod_owner
)
# 按机房看错误率
sum by (region) (rate(http_requests_total{status=~"5.."}[5m]))5.6 一对多:group_right
group_right 是 group_left 的镜像——用于「一的那侧在左边」的情况:
# 左侧一条(粗),右侧多条(细)
sum by (job) (rate(http_requests_total[5m]))
/ on(job) group_right
sum by (job, status) (rate(http_requests_total{status=~"5.."}[5m]))实践中 group_right 用得少得多,因为你总可以把表达式左右调换,改用 group_left。而 group_left 更符合「左侧是主体,右侧是补充信息」的直觉。
建议:优先写成 group_left 的形式。 如果发现需要 group_right,先想想能不能把两边换个位置。
5.7 常见报错与排查
报错一:many-to-many matching not allowed
found duplicate series for the match group {job="api"} on the right hand-side原因:右侧在匹配标签上不唯一。可能是:
- 忘了写
group_left(本来就是多对一) on()里列的标签不够,导致右侧有多条序列落在同一个匹配组
排查方法:单独执行右侧表达式,看在 on() 指定的标签下是否唯一:
# 检查右侧是否每个 job 只有一条
count by (job) (你的右侧表达式)
# 如果结果 > 1,就是这里的问题报错二:结果为空(不报错)
原因:两边在匹配标签上没有交集。
排查方法:分别执行两侧,对比标签值。
# 左侧有哪些 job
count by (job) (左侧表达式)
# 右侧有哪些 job
count by (job) (右侧表达式)
# 如果一个是 "api" 一个是 "api-server",那就找到原因了报错三:multiple matches for labels(group_left 之后仍报错)
原因:加了 group_left 但两边都多,是真正的「多对多」。PromQL 不支持多对多。
解决:先用聚合把其中一侧变成一对一。
遇到向量匹配问题,永远先把表达式拆成两半单独执行:
# 第 1 步:单独跑左侧,记下返回的标签
sum by (job, status) (rate(http_requests_total{status=~"5.."}[5m]))
# 第 2 步:单独跑右侧,记下返回的标签
sum by (job) (rate(http_requests_total[5m]))
# 第 3 步:对比标签集
# 完全相同 → 直接运算,不需要 on/ignoring
# 左多右少 → on(共同标签) group_left
# 左少右多 → on(共同标签) group_right
# 有标签名不一致 → 先用 label_replace 改名90% 的向量匹配问题在第 3 步就能定位。
6. 综合实战
6.1 完整的错误率查询
# 版本 1:全局错误率(最简单,不需要向量匹配)
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))# 版本 2:按服务分组(两边标签一致,仍不需要 on)
sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
/
sum by (job) (rate(http_requests_total[5m]))# 版本 3:按服务分组,同时展开状态码明细(需要 group_left)
sum by (job, status) (rate(http_requests_total{status=~"5.."}[5m]))
/ on(job) group_left
sum by (job) (rate(http_requests_total[5m]))# 版本 4:加上防误报条件和百分比转换
100 * (
sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
/ sum by (job) (rate(http_requests_total[5m]))
)
and
sum by (job) (rate(http_requests_total[5m])) > 10sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
/ sum by (job) (rate(http_requests_total[5m]))如果某个 job 完全没有 5xx(这是好事!),那么分子里就没有这个 job 的序列,除法配对失败,结果里这个 job 直接消失——而不是显示 0。
在 Grafana 大盘上的表现是:健康的服务反而不显示,只有出过错的服务才有线。很反直觉。
修正:给分子加一个 or 兜底。
(
sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
or
sum by (job) (rate(http_requests_total[5m])) * 0
)
/
sum by (job) (rate(http_requests_total[5m]))* 0 那一项构造出了「所有 job 的 0 值序列」,or 让它只在分子缺失时生效。
6.2 rate 比值:计算平均值和单位成本
# 平均请求耗时(Histogram 的 sum / count)
rate(http_request_duration_seconds_sum[5m])
/
rate(http_request_duration_seconds_count[5m])这两个序列的标签完全一致(只有指标名不同),所以是标准的一对一匹配,不需要 on。
# 每个请求平均消耗的 CPU 时间
rate(process_cpu_seconds_total[5m])
/ on(instance, job)
rate(http_requests_total[5m])这里需要 on,因为 http_requests_total 上还有 method、status 等标签。更好的写法是先聚合:
sum by (instance) (rate(process_cpu_seconds_total[5m]))
/ sum by (instance) (rate(http_requests_total[5m]))6.3 缓存命中率
sum by (cache) (rate(cache_operations_total{result="hit"}[5m]))
/
sum by (cache) (rate(cache_operations_total{op="get"}[5m]))注意分子分母来自同一个指标的不同切片,标签在 by (cache) 之后完全一致,一对一匹配成功。
6.4 Apdex 分数
Apdex(Application Performance Index)是一个业界标准的满意度指标:
Apdex = (满意数 + 容忍数 / 2) / 总数
满意:耗时 ≤ T
容忍:T < 耗时 ≤ 4T
不满意:耗时 > 4T设 T = 0.3 秒(那么 4T = 1.2 秒,取最近的桶 1 秒):
(
sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m]))
+ sum(rate(http_request_duration_seconds_bucket{le="1"}[5m]))
) / 2
/
sum(rate(http_request_duration_seconds_count[5m]))推导:满意数 = le="0.3" 的桶;满意+容忍 = le="1" 的桶。
(满意 + 容忍/2) = (满意 + (le1 - 满意)/2) = (满意 + le1) / 2所以直接把两个桶相加除以 2 即可,非常优雅。
给定以下数据:
向量 A:
cpu_usage{instance="web-1", region="bj"} 85
cpu_usage{instance="web-2", region="bj"} 30
cpu_usage{instance="web-3", region="sh"} 92
向量 B:
cpu_limit{instance="web-1", region="bj"} 100
cpu_limit{instance="web-2", region="bj"} 100
cpu_limit{instance="web-4", region="sh"} 100
向量 C:
in_maintenance{instance="web-3"} 1写出以下每个表达式的完整结果(含标签和值):
1. cpu_usage / cpu_limit
2. cpu_usage > 80
3. cpu_usage > bool 80
4. cpu_usage and cpu_limit
5. cpu_usage or cpu_limit
6. cpu_usage unless on(instance) in_maintenance
7. cpu_usage > 80 unless on(instance) in_maintenance
8. cpu_usage * 2下面 4 个查询都无法得到预期结果,请诊断并修正:
# 1. 想算:每个状态码占总请求的比例
sum by (status) (rate(http_requests_total[5m]))
/
sum(rate(http_requests_total[5m]))
# 2. 想算:每个 Pod 的内存使用率
container_memory_usage_bytes / container_spec_memory_limit_bytes
# 3. 想算:按 K8s Deployment 汇总的 CPU 使用量
sum by (deployment) (rate(container_cpu_usage_seconds_total[5m]))
# 4. 想算:排除测试环境后的错误率
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
unless
http_requests_total{env="test"}你的环境中有以下指标:
# 应用 QPS(只有 instance 和 job 标签)
http_requests_total{job="api", instance="10.0.1.5:8080", status="200"}
# 构建信息(info 指标,值恒为 1)
app_build_info{job="api", instance="10.0.1.5:8080", version="2.3.1", commit="a1b2c3", team="order"} 1
# 实例元数据
instance_meta{instance="10.0.1.5:8080", region="bj", az="bj-a", machine_type="c6.xlarge"} 1完成以下需求:
- 按
version分组统计 QPS,用于观察灰度发布的流量分配。 - 按
region分组统计 5xx 错误率。 - 找出「运行着 2.3.0 旧版本」且「QPS 大于 10」的实例。
- 解释为什么
group_left里的标签列表不能省略。
为一个微服务集群设计告警,要求:
- 服务 5xx 错误率超过 2% 时告警,但要避免低流量误报。
- 正在进行滚动发布的服务不告警(发布状态由
deploy_in_progress指标表示,值为 1)。 - 标记为
tier="experimental"的服务用更宽松的阈值(10%)。 - 告警信息里要带上服务的负责团队(来自
service_meta指标的team标签)。
已有指标:
http_requests_total{job, instance, status}
deploy_in_progress{job} 1
service_meta{job, team, tier} 1写出完整的告警规则,并解释每个部分的设计考量。
小结
- 算术运算(
+ - * / % ^):向量↔标量作用于每条序列;向量↔向量要求标签集完全一致才能配对,配不上的序列消失。所有算术运算都会删除指标名。 - 比较运算默认是过滤器(保留满足条件的序列,值和标签原样不变);加
bool变成求值(全部保留,值变 0/1,删除指标名)。标量之间比较必须加bool。 - 逻辑运算
and(交集)/or(并集,左侧优先)/unless(差集)只看标签不看值,是告警加条件、加静默的主力工具。 - 向量匹配是难点:用
on(...)指定匹配用的标签,或ignoring(...)指定忽略的标签。 group_left/group_right声明「多」在哪一侧;group_left(labels)还能把右侧的标签复制到结果里,这是关联 info 指标(版本、团队、机房)的标准手法。- 遇到匹配问题,把表达式拆成两半单独执行,对比标签集——90% 的问题在这一步就能定位。
- 「比值型」表达式在分子为空时整条序列会消失,用
or ... * 0兜底。 - 告警上线后要检查
/api/v1/rules里的health,向量匹配错误不会让 Prometheus 崩溃,只会让规则静静失效。