rate(counter[5m]) = 过去 5 分钟平均每秒涨多少——区间测速。QPS、错误率、GC 回收速率全出自它。覆盖 核心盘全部速率曲线(c16 / c17 / c12 / c13 / c35…)的原理课。
交警查超速不用盯着你的车,只在高速上设两个卡口:10:05 记下里程、10:10 再记一次,里程差 ÷ 时间差 = 区间平均速度。rate(counter[5m]) 干的就是这件事:对「只增不减」的里程表(Counter),算过去 5 分钟平均每秒涨了多少。请求数每秒涨几个,QPS 就是几。
它是四两监控里出镜率最高的函数:QPS(c16/c12)、错误率(c13/c17)、锁冲突率(c35)、GC 回收速率(m126)……全是 rate 加工出来的。它还自带一个保命特性:服务重启时 Counter 归零的断崖会被自动补齐——这正是「必须用 rate、不能自己相减」的原因。
想象高速区间测速。卡口 A 在 10:05 拍下你的车,里程表读数 12000 公里;卡口 B 在 10:10 再拍一次,读数 12300 公里。5 分钟跑了 3 公里,除以时间就是平均时速 36 km/h。注意:没有任何人在意「里程表绝对读数」——12000 还是 12300 本身毫无意义,有意义的只有差值除以时间。
rate(x[5m]) 就是这套流程的自动化版本。Prometheus 每 15 秒(核心盘)或 30 秒来「拍一次照」抄下 Counter 读数;rate 在每个时刻往回看 5 分钟的窗口,取窗口内首尾两个读数相减、除以秒数,得到「平均每秒涨多少」。对 siliang_http_requests_total 来说,每秒涨 1 个请求就是 1 QPS;对 siliang_llm_calls_total 来说就是每秒几个大模型调用。窗口里点越多,平均越稳——这就是「[5m] 平滑、[1m] 抖」的全部原因。
还有个隐藏难题:服务半夜重启,里程表「哐当」归零重新计数。如果你自己拿两个读数相减,会算出一个巨大的负数——速度是负的,物理上都说不通。rate 在内部专门处理了这种「计数器重置」:发现后一个读数比前一个小时,就知道是重启了,自动把差值修正回来。所以区间测速这件事,永远交给 rate,不要自己算。
| 类比里的东西 | 系统里对应的东西 |
|---|---|
| 两个卡口记下的里程读数 | Counter 两次抓取的累计值(如 12000 → 12300) |
| (里程差 ÷ 时间差) = 平均时速 | rate(x[5m]) = (末读数 − 首读数) ÷ 300 秒 |
| 时速 36 km/h | 1 req/s —— QPS(每秒请求数) |
| 区间拉得越长,平均越稳 | [5m] 窗口越大曲线越平滑;[1m] 越抖越灵敏(临时排查用) |
| 中途抛锚重启,里程表照常连续 | 服务重启 Counter 归零,rate 自动补齐断崖,曲线不断 |
siliang_http_requests_total{job="siliang_backend_worker"}:一条永远上扬的斜线,没有节奏感。sum(rate(siliang_http_requests_total[5m])) by (handler):立刻变成有起伏的 QPS 曲线,这就是 c16 面板的口径。rate(...{outcome="error"}[5m]) / clamp_min(rate(...[5m]), 1)——错误率就是「错误的速度 ÷ 总速度」。# 对: Counter 配 rate rate(siliang_http_requests_total[5m]) # 错: Gauge 配 rate(内存是温度计) rate(siliang_process_resident_memory_bytes[5m]) # ✗
# GC 垃圾车每秒出车几次(m126 口径,按代拆) sum by (generation) (rate(siliang_python_gc_collections_total[5m]))
# 同一指标,两种窗口 rate(siliang_http_requests_total[5m]) # 平滑,看趋势 rate(siliang_http_requests_total[1m]) # 抖,对时间戳用
# 重启瞬间:原始 Counter 砸到 0(上节课的断崖) siliang_http_requests_total # → 归零 # rate 之后:曲线平滑过渡,无负数、无深坑 rate(siliang_http_requests_total[5m]) # → 自动补齐
clamp_min(..., 1) 防止没流量时除零出 NaN(c13 口径原文)。
# c13 LLM 错误率口径(照抄) rate(siliang_llm_calls_total{outcome="error"}[5m]) / clamp_min(rate(siliang_llm_calls_total[5m]), 1)
sum(rate(...)) by (handler):先对每个 worker 的速率求和(4 个 worker 加总才是全站),再按维度拆线。忘了 by 就全站一条线,看不出谁出事。
# c16 口径:按接口拆开,谁最忙一目了然 sum(rate(siliang_http_requests_total[5m])) by (handler)
# 同一事实的两种问法 rate(x[1h]) # 平均每秒涨多少 increase(x[1h]) # 这 1 小时总共涨多少
job="siliang_backend_worker" 排除。手写查询不带它,速率直接翻倍。
# 标准姿势:过滤 + 求和 + 拆维度 sum(rate(siliang_llm_calls_total{job="siliang_backend_worker"}[5m])) by (provider)
① HTTP QPS(按 handler)(c16)——rate 的头号应用:sum(rate(siliang_http_requests_total[5m])) by (handler),谁是最忙接口一目了然:🟢 HTTP QPS(按 handler)↗
② HTTP 5xx 错误率(c17)——只把 5xx 的速率捞出来,>1 req/s 就该排查:🟢 HTTP 5xx 错误率 ↗
③ LLM 错误率(c13)——两个速率相除 + clamp_min 防除零;>10% 多半是 provider 限流或 key 失效:🟡 LLM 错误率(按 provider)↗
④ 数据库锁冲突率(c35)——按 operation × code 拆开的速率曲线,1205/1213 哪类操作在抢锁一看便知:🔴 数据库锁冲突率 ↗
⑤ LLM 调用 QPS(c12)——钱花在哪家的实时账,也是活动/被刷时最先动的线:🟡 LLM 调用 QPS(按 provider)↗
想自己搭一块「谁最忙」的图,照抄 c16 口径即可,一个字符都不用改。
# HTTP QPS 按 handler 拆线(c16 口径) sum(rate(siliang_http_requests_total{job="siliang_backend_worker"}[5m])) by (handler) # 判读:无发布无活动却突增 = 被刷/爬虫; # 某接口流量突然归零 = 上游调用方挂了
c17 的判读口径是「>1 req/s 就该看日志」。把它写成 Prometheus 告警规则,半夜自动叫人。
# 5xx 速率告警(阈值来自 c17 口径:>1 req/s 排查) groups: - name: siliang-http rules: - alert: Http5xxRateHigh expr: | sum(rate(siliang_http_requests_total{status=~"5..",job="siliang_backend_worker"}[5m])) by (handler) > 1 for: 5m # 持续 5 分钟才响,抖一下不吵人 labels: severity: warning annotations: summary: "{{ $labels.handler }} 5xx 超 1 req/s,直奔日志"
c13 >10% 红。看到红线先分清「我们的错」还是「供应商的错」——429 限流和 key 失效都不改代码能解决。
# 错误率(c13 口径),按 provider 拆开 rate(siliang_llm_calls_total{outcome="error",job="siliang_backend_worker"}[5m]) / clamp_min(rate(siliang_llm_calls_total{job="siliang_backend_worker"}[5m]), 1) # 判读:>10% 且集中在某家 provider → 限流(429)/key 失效; # 动作是切流量或换 key,不是翻我们自己的代码
「10:23 那一下到底发生了什么」——[5m] 的平滑会抹掉凶案现场,临时缩到 [1m] 配合日志时间戳。
# 平时看趋势:5m 窗口(面板口径) sum(rate(siliang_ws_connections_total[5m])) by (outcome) # 排查瞬间:缩到 1m,尖刺显形(c21 口径的排查变体) sum(rate(siliang_ws_connections_total[1m])) by (outcome) # 用完改回 [5m]——1m 抖动大,不适合长期盯着
同一个 rate,按不同 label 拆开回答完全不同的问题——这就是 label 的威力(第 13 课细讲)。
# 谁最忙:按接口拆(c16) sum(rate(siliang_http_requests_total[5m])) by (handler) # 钱花在哪:按供应商拆(c12) sum(rate(siliang_llm_calls_total[5m])) by (provider) # LLM 延迟分桶要按 model 拆(c15 的桶口径) sum(rate(siliang_llm_call_duration_seconds_bucket[5m])) by (le, model)
# 错: rate(siliang_ws_online_users[5m]) # → 无意义的数 # 对: sum(siliang_ws_online_users{job="siliang_backend_worker"})
# 错: (now − before) / 300 # → 重启后出负数 # 对: rate(siliang_http_requests_total[5m]) # → 自动补齐断崖
rate(x[15s]) 之类的曲线断断续续缺数据。原因:核心盘抓取 15s、其余 30s,窗口里至少要有 2 个点才能算差值。正解:窗口别小于 1m;默认 [5m],排查临时 [1m]。
# 错: rate(siliang_http_requests_total[15s]) # → 点太少,算不动 # 对: rate(siliang_http_requests_total[5m]) # → 默认口径
# 错: sum(rate(siliang_http_requests_total[5m])) # → 只有一条线 # 对: sum(rate(...[5m])) by (handler) # → 每接口一条
# 错: rate(errors[5m]) / rate(total[5m]) # → 0/0 = NaN # 对: rate(errors[5m]) / clamp_min(rate(total[5m]), 1)
# 错: sum(rate(siliang_llm_calls_total[5m])) # → 双计 # 对: sum(rate(siliang_llm_calls_total{job="siliang_backend_worker"}[5m]))
# 错: 拿 rate(x[5m]) 回答「一天多少请求」 # → 单位都不对 # 对: sum(increase(siliang_http_requests_total[24h])) # → 总量
参考答案:交警在高速两头各拍一张里程表读数,里程差除以时间就是区间平均时速。rate(counter[5m]) 就是取过去 5 分钟窗口里 Counter 的首尾两个读数相减、除以 300 秒,得到「平均每秒涨多少」——对请求数来说就是 QPS。
参考答案:窗口越大,参与「平均」的抓取点越多,单点毛刺被摊薄,曲线越稳——代价是反应迟钝;窗口小则贴近瞬间、抖动大。平时看趋势用默认 [5m];要回答「10:23 那一下发生了什么」时临时缩到 [1m] 对齐日志时间戳,用完改回来。
参考答案:Counter 重启归零。自己拿两个读数相减会得到巨大负数——速率是负的,物理上说不通,曲线上是一个深坑。rate 识别到「后一个读数比前一个小」就知道发生了重置,自动按重置后的增量修正,曲线平滑过渡,不需要人操心。
参考答案:分子是 rate(错误数[5m])——每秒错几个;分母是 rate(总数[5m])——每秒总共几个,相除就是错误比例(c13 口径)。深夜或低谷没流量时分母可能是 0,0÷0 会得到 NaN 让曲线断线,clamp_min(..., 1) 把分母兜底到至少 1,没流量时错误率按 0 处理。