⏱️ rate():从里程表算出速度

rate(counter[5m]) = 过去 5 分钟平均每秒涨多少——区间测速。QPS、错误率、GC 回收速率全出自它。覆盖 核心盘全部速率曲线(c16 / c17 / c12 / c13 / c35…)的原理课。

rate():区间测速 —— 从里程表读数算出每秒速度 rate(counter[5m]) = 过去 5 分钟平均每秒涨多少 = QPS 里程表:Counter 两次抓拍 t=10:05:00 → 累计 12000 次 t=10:10:00 → 累计 12300 次 5 分钟涨了 300 次 Prometheus 每 15/30s 来抄一次读数 核心盘 15s · 其余 30s 读数本身没有意义,有意义的是差值 rate(x[5m]) 区间测速仪 (12300 − 12000) ÷ 300 秒 = 每秒 1 次 = 1 QPS 附赠保命特性:自动补齐重启归零 每个时刻往回看 5 分钟, 取窗口内首尾两个读数相减再除秒数 输出的永远是「平均每秒」,带单位才好懂 输出:QPS 曲线 有起伏、可判读:高峰低谷一眼可见 ✅ 健康路:交给 rate 的两件事 ① 窗口越大越平滑,越小越抖 [5m] 平滑(面板默认) [1m] 抖(临时排查细节) ② 服务重启 Counter 归零?rate 自动补齐 重启归零断崖被虚线桥接,曲线不断 排查细节临时缩 [1m],平时一律 [5m] 🚨 危险路:自己手动相减 💥 事故现场:(当前读数 − 5 分钟前读数) ÷ 300 服务一重启 Counter 归零 → 差值变成巨大负数 曲线出现深坑,速率还是负的——物理上都说不通 重启瞬间:负数深坑 正解:永远用 rate(),让 Prometheus 替你处理重置

💡 一句话理解

交警查超速不用盯着你的车,只在高速上设两个卡口: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/h1 req/s —— QPS(每秒请求数)
区间拉得越长,平均越稳[5m] 窗口越大曲线越平滑;[1m] 越抖越灵敏(临时排查用)
中途抛锚重启,里程表照常连续服务重启 Counter 归零,rate 自动补齐断崖,曲线不断

🛠 动手验证(4 步,在 Grafana Explore 里亲手做一遍)

  1. 先看里程表本表——查询 siliang_http_requests_total{job="siliang_backend_worker"}:一条永远上扬的斜线,没有节奏感。
  2. 套上区间测速——改成 sum(rate(siliang_http_requests_total[5m])) by (handler):立刻变成有起伏的 QPS 曲线,这就是 c16 面板的口径。
  3. 把 [5m] 改成 [1m]——曲线变毛糙、尖刺变多:窗口越小越贴近瞬间、越抖;再改回 [5m] 恢复平滑。
  4. 看错误率怎么来的——打开 c13 卡看口径:rate(...{outcome="error"}[5m]) / clamp_min(rate(...[5m]), 1)——错误率就是「错误的速度 ÷ 总速度」。

🧠 必知必会 看懂本课全部面板的地基

rate 只喂 Counter
rate 的数学前提是「只增不减 + 重启归零可识别」。内存、在线人数这种 Gauge 喂进去,输出是没有任何物理意义的数字。
# 对: Counter 配 rate
rate(siliang_http_requests_total[5m])
# 错: Gauge 配 rate(内存是温度计)
rate(siliang_process_resident_memory_bytes[5m])  # ✗
输出是「平均每秒」
rate 的结果永远带「每秒」单位:请求数 → QPS,错误数 → 错误速率,出车次数 → 每秒 GC 频率。报数字时带上单位,才不会闹「QPS 等于 0.03 个请求」的误会。
# GC 垃圾车每秒出车几次(m126 口径,按代拆)
sum by (generation) (rate(siliang_python_gc_collections_total[5m]))
[5m] 是默认窗口
窗口越大平均越平滑、越不灵敏;越小越贴近瞬间、越抖。面板默认 [5m];抓排查细节时临时改 [1m]。窗口至少要盖住两个抓取点(核心盘 15s、其余 30s)。
# 同一指标,两种窗口
rate(siliang_http_requests_total[5m])  # 平滑,看趋势
rate(siliang_http_requests_total[1m])  # 抖,对时间戳用
重启归零自动补齐
rate 发现窗口内读数「变小」就判定发生了 Counter 重置,自动按重置后的增量修正——所以断崖在 rate 之后的曲线上是平滑过渡,不会出现负数。
# 重启瞬间:原始 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 再 by
面板惯例写法 sum(rate(...)) by (handler):先对每个 worker 的速率求和(4 个 worker 加总才是全站),再按维度拆线。忘了 by 就全站一条线,看不出谁出事。
# c16 口径:按接口拆开,谁最忙一目了然
sum(rate(siliang_http_requests_total[5m])) by (handler)
rate 的兄弟 increase
rate 回答「每秒多快」,increase 回答「窗口内总共涨多少」(≈ rate × 窗口秒数)。看趋势用 rate,报总量(如「近 1h 锁冲突几次」)用 increase——下一课细讲。
# 同一事实的两种问法
rate(x[1h])      # 平均每秒涨多少
increase(x[1h])  # 这 1 小时总共涨多少
别忘了 job 过滤
共享端口的 server job 随机命中 worker 会双计,四两所有面板都带 job="siliang_backend_worker" 排除。手写查询不带它,速率直接翻倍。
# 标准姿势:过滤 + 求和 + 拆维度
sum(rate(siliang_llm_calls_total{job="siliang_backend_worker"}[5m])) by (provider)

🔗 在四两监控里哪里用到 理论落回你的 89 张图

① 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)↗

🏭 生产实战 real world

场景 1 · 复刻核心盘的 QPS 面板

想自己搭一块「谁最忙」的图,照抄 c16 口径即可,一个字符都不用改。

# HTTP QPS 按 handler 拆线(c16 口径)
sum(rate(siliang_http_requests_total{job="siliang_backend_worker"}[5m]))
  by (handler)

# 判读:无发布无活动却突增 = 被刷/爬虫;
#       某接口流量突然归零 = 上游调用方挂了

场景 2 · 给 5xx 速率写一条告警

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,直奔日志"

场景 3 · LLM 错误率:别把限流当 bug 修

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,不是翻我们自己的代码

场景 4 · 排查毛刺:临时缩窗口对时间戳

「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 抖动大,不适合长期盯着

场景 5 · 切维度三连:by handler / provider / model

同一个 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)

⚠️ 常见坑 pitfalls

坑 1 · 给 Gauge 套 rate — 症状:内存、在线人数套 rate 后输出的数字小数点后一长串,忽正忽负。原因:rate 只对「只增不减」的里程表有意义。正解:Gauge 直接读,看趋势用 deriv(第 6 课)。
# 错: rate(siliang_ws_online_users[5m])              # → 无意义的数
# 对: sum(siliang_ws_online_users{job="siliang_backend_worker"})
坑 2 · 自己手动相减算速率 — 症状:自建面板在服务重启后出现负数深坑。原因:手算不识别 Counter 重置,归零被当成了「暴跌」。正解:永远用 rate(),重置补齐它包了。
# 错: (now − before) / 300                 # → 重启后出负数
# 对: rate(siliang_http_requests_total[5m]) # → 自动补齐断崖
坑 3 · 窗口小于两倍抓取间隔 — 症状:rate(x[15s]) 之类的曲线断断续续缺数据。原因:核心盘抓取 15s、其余 30s,窗口里至少要有 2 个点才能算差值。正解:窗口别小于 1m;默认 [5m],排查临时 [1m]。
# 错: rate(siliang_http_requests_total[15s])  # → 点太少,算不动
# 对: rate(siliang_http_requests_total[5m])   # → 默认口径
坑 4 · 忘了 by (handler) 全站一条线 — 症状:QPS 图只有一条总曲线,出事后不知道哪个接口背锅。原因:sum 把所有维度加没了。正解:sum 后必接 by (你要看的维度)。
# 错: sum(rate(siliang_http_requests_total[5m]))              # → 只有一条线
# 对: sum(rate(...[5m])) by (handler)                          # → 每接口一条
坑 5 · 相除没有 clamp_min — 症状:深夜没流量时错误率曲线断线或出现 NaN 缺口。原因:0 ÷ 0 在浮点世界是 NaN。正解:分母套 clamp_min(..., 1)(c13 口径原文)。
# 错: rate(errors[5m]) / rate(total[5m])            # → 0/0 = NaN
# 对: rate(errors[5m]) / clamp_min(rate(total[5m]), 1)
坑 6 · 忘带 job 过滤,速率翻倍 — 症状:自建面板的 QPS 比官方面板高一倍。原因:共享端口的 server job 把每个请求数了两遍。正解:查询永远带 job="siliang_backend_worker"。
# 错: sum(rate(siliang_llm_calls_total[5m]))                       # → 双计
# 对: sum(rate(siliang_llm_calls_total{job="siliang_backend_worker"}[5m]))
坑 7 · 把 rate 的输出当「总数」汇报 — 症状:周报里写「我们的 QPS 是 3.7」,老板问「所以一天多少单?」答不上来。原因:rate 是速度不是总量。正解:报总量用 increase(下一课):increase(x[24h]) = 昨天一整天涨了多少。
# 错: 拿 rate(x[5m]) 回答「一天多少请求」  # → 单位都不对
# 对: sum(increase(siliang_http_requests_total[24h]))  # → 总量

🎓 费曼自测 合上书,能讲出来才算懂

Q1 · 用区间测速,一句话解释 rate(counter[5m]) 在算什么?

参考答案:交警在高速两头各拍一张里程表读数,里程差除以时间就是区间平均时速。rate(counter[5m]) 就是取过去 5 分钟窗口里 Counter 的首尾两个读数相减、除以 300 秒,得到「平均每秒涨多少」——对请求数来说就是 QPS。

Q2 · 为什么窗口越大越平滑?什么时候临时缩小窗口?

参考答案:窗口越大,参与「平均」的抓取点越多,单点毛刺被摊薄,曲线越稳——代价是反应迟钝;窗口小则贴近瞬间、抖动大。平时看趋势用默认 [5m];要回答「10:23 那一下发生了什么」时临时缩到 [1m] 对齐日志时间戳,用完改回来。

Q3 · 服务重启瞬间,自己相减和 rate 各会发生什么?

参考答案:Counter 重启归零。自己拿两个读数相减会得到巨大负数——速率是负的,物理上说不通,曲线上是一个深坑。rate 识别到「后一个读数比前一个小」就知道发生了重置,自动按重置后的增量修正,曲线平滑过渡,不需要人操心。

Q4 · 错误率的分子分母各是什么?分母为什么要 clamp_min?

参考答案:分子是 rate(错误数[5m])——每秒错几个;分母是 rate(总数[5m])——每秒总共几个,相除就是错误比例(c13 口径)。深夜或低谷没流量时分母可能是 0,0÷0 会得到 NaN 让曲线断线,clamp_min(..., 1) 把分母兜底到至少 1,没流量时错误率按 0 处理。

← 上一课:Counter 计数器 vs Gauge 温度计 📚 课程目录 下一课:increase() 与 changes() →