🧮 increase() 与 changes():涨了多少 vs 变了几次

里程表的两个衍生问题:increase 问「这小时总共涨了多少」(总量),changes 问「值变了几次手」(次数)。覆盖 c34 锁冲突哨兵 / k5 容器重启等「数次数」面板的原理课。

increase() 与 changes():涨了多少 vs 变了几次 类比:里程表这小时跑了几公里(increase) vs 里程表被动过几次手(changes) increase(x[1h]):这小时总共涨了多少 总量版 ≈ rate × 窗口秒数 增量 +N 只关心首尾差值:过程再曲折,总量一句话说清 changes(x[1h]):值变了几次手 数变动次数,不管涨跌多少、方向朝哪 变手 ① 变手 ② 启动时间戳跳一次 = 容器重启一次,数的就是跳变 四两落地:🔴 数据库锁冲突(近 1h 次数) sum(increase(siliang_db_lock_errors_total {job="siliang_backend_worker"}[1h])) L-21 锁专项修复的复发哨兵:删除/换源重试每触发一次计一次 ✅ 修干净恒为 0(<1 绿) 🚨 ≥1 黄 / ≥10 红(c34 口径) 四两落地:🟡 容器重启次数(k5 口径) changes(container_start_time_seconds[1h]) 启动时间戳是个状态值,画绝对值没意义; 数它变了几次手,就是重启了几次 ✅ 发布窗口内重启属预期 🚨 窗口外 >0 = 计划外重启,要查 ✅ 健康路:哨兵恒 0 锁冲突近 1h = 0 · 重启只发生在发布窗口内 一条贴地的平线,就是最好的消息 🚨 危险路:哨兵非零 increase ≥1 = 冒出新锁窗口 · changes >0 = 计划外重启 尖刺出现就对照发布记录,窗口外即立案

💡 一句话理解

面对一个只涨不跌的里程表,人类只问两个问题:「这小时总共跑了几公里?」——这是 increase(x[1h]),窗口内总共涨了多少(总量);「这表被动过几次手?」——这是 changes(x[1h]),值变了几次(次数)。一个关心幅度,一个关心频率。

四两里最有名的两个「数次数」面板全靠它们:锁冲突「近 1h 次数」(c34)用 increase 数过去一小时吵了几次架,是锁专项修复的复发哨兵;容器重启次数(k5)用 changes 数启动时间戳跳了几次。它们的共同气质是:曲线长期贴地、非零即事发——这是监控里最好用的一种图。

🧩 费曼拆解 讲给完全没接触过的小白

出租车司机交班,公司问的第一个问题是「这一班跑了多少公里?」——司机看一眼里程表:出车时 38217,收车时 38462,跑了 245 公里。注意司机不需要回忆每一脚油门,只看首尾两个读数的差。这就是 increase(x[1h]):把过去 1 小时窗口内「总共涨了多少」一次算清。它其实就是上一课 rate 的总量版:每秒涨 0.07 次 × 3600 秒 ≈ 250 次。

修理厂问的则是另一个问题:「这表被动过没有?」——他们不关心公里数,关心的是读数有没有被改写:调表一次,读数就「变一次手」。 changes(x[1h]) 数的就是这个:窗口内值变了几次,涨变算一次、跌变也算一次、涨 1 还是涨 100 万都只算一次。妙的是它能用在不是计数器的数上:容器一重启,container_start_time_seconds(本次启动的时间戳)就跳成一个新值——所以数它变几次手,就是数容器重启了几次(k5 面板口径)。

两个函数在四两的用法高度一致:当哨兵。锁冲突哨兵 c34 用 sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h]))——L-21 锁专项修复后应恒为 0,一旦非零说明有新的锁窗口冒出来;重启哨兵用 changes——发布窗口内重启属预期,窗口外重启就是计划外事故。哨兵图最好看:一条贴地的平线就是最好的消息。

类比里的东西系统里对应的东西
这班跑了几公里(看首尾差)increase(x[1h]):窗口内总共涨多少(≈ rate × 3600 秒)
里程表被拨动过几次changes(x[1h]):值变了几次手(只数次数,不看幅度方向)
计费里程(增量)c34 近 1h 锁冲突次数 = sum(increase(siliang_db_lock_errors_total[1h]))
表动过 = 有人动过车changes(container_start_time_seconds[1h]) = 容器重启次数(k5)
公里数大 ≠ 表被拨过一个值涨得再多 changes 可以是 0;跳变一次 changes 才 +1

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

  1. 跑锁冲突哨兵——sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h])):健康时应恒为 0(L-21 修复干净),这数字应与 c34 大数字一致。
  2. 切换速率视角对比——同指标改 sum(rate(...)) by (operation, code):这就是 c35 的口径;体会「increase 看总量、rate 看速度」是同一事实的两种问法。
  3. 数一次容器重启——count by (service) (changes(container_start_time_seconds[1h])):每个 service 近 1h 重启几次,发布窗口外应为 0。
  4. 对照 k5 面板口径——打开主图谱 k5 卡:两条线正是 changes(container_start_time_seconds[1h]) + increase(container_oom_events_total[1h]),本课两个函数同框出演。

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

increase = 窗口内总量
rate 回答「每秒多快」,increase 回答「这段总共多少」,数值上 increase ≈ rate × 窗口秒数。报警看速率、汇报看总量、哨兵看「窗口内有没有发生过」。
# 近 1 小时总共发生几次锁冲突(c34 口径)
sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h]))
changes = 变动次数
只数「值变了几次」,涨变跌变都算、幅度大小不限、一次就是一次。它不回答「涨了多少」——那是 increase 的活。
# 数变动次数(不管变成多少)
changes(container_start_time_seconds[1h])
# 跳 2 次 = 2;哪怕其中一次只差 1 毫秒的时间戳
changes 对「状态值」特别好用
container_start_time_seconds 不是计数器,是「本次启动的时间戳」——画绝对值是一条死平线。但每次重启它必跳一次,所以数跳变 = 数重启。这是一种通用套路:死数变活数。
# 时间戳(死值)→ 重启次数(活数)
count by (service) (changes(container_start_time_seconds[1h]))
increase 与 rate 的换算
increase(x[1h]) ≈ rate(x[1h]) × 3600。选哪个看你想回答什么:图上盯节奏用 rate,大数字面板报「近 1h 几次」用 increase。
# 同一事实,两种问法
rate(x[1h])      # 平均每秒 0.05 次
increase(x[1h])  # 这一小时总共约 180 次
哨兵 = 函数 + 非零即报
哨兵的灵魂在阈值语义:普通指标看高低,哨兵只看「是不是 0」。锁冲突哨兵 c34 的口径:<1 绿 / ≥1 黄 / ≥10 红——因为它衡量的是「修复有没有被回退」,发生 1 次就该看。
# 哨兵告警思路:非 0 即报(阈值口径以 c34 卡为准)
sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h])) > 0
窗口决定「记忆长度」
[1h] 的哨兵只记得最近一小时。想看「今天有没有发生过」,把窗口拉到 [12h]/[24h];代价是重启归零等扰动也一起被平均,哨兵口径要跟业务对齐。
# 今天 0 点以来总共几次(口径示例)
sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[12h]))
别忘了 job 过滤 + 汇总
锁冲突可能发生在任意 worker 上,所以先 job 过滤排除 server job 双计,再 sum 把 4 个 worker 加总——少一步数字就不可信。
# 标准姿势:过滤 → increase → sum
sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h]))

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

① 数据库锁冲突(近 1h 次数)(c34)——increase 哨兵的头号案例:近 1 小时吵架几次,L-21 修复后恒为 0,≥1 黄 / ≥10 红:🔴 数据库锁冲突(近 1h 次数)↗

② 数据库锁冲突率(c35)——increase 的姊妹视角:用 rate 按 operation × code 拆开,定位「谁在跟谁抢」:🔴 数据库锁冲突率(按操作与错误码)↗

③ 容器重启与 OOM 事件(k5)——changes 与 increase 同框:重启次数(changes 启动时间戳)+ OOM 事件数(increase(container_oom_events_total[1h])):🟡 容器重启与 OOM 事件 ↗

④ 容器盘口径卡(k0)——「重启次数 = 启动时间戳在窗口内变化」这条口径说明就写在卡里,是 changes 用法的官方出处:📖 容器盘口径说明(先读我)↗

🏭 生产实战 real world

场景 1 · 锁冲突复发哨兵告警

L-21 修复上线后的「防回退警报器」:非 0 即异常,不用等人半夜发现接口变慢。

# 锁冲突哨兵告警(阈值口径以 c34「先读我」为准:≥1 黄 / ≥10 红)
groups:
- name: siliang-db-locks
  rules:
  - alert: DbLockErrorRecur
    expr: |
      sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h])) > 0
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "近 1h 锁冲突非零,疑似锁窗口复发,去 c35 定位操作与错误码"

判读收尾:告警响了先开 c35——1205(锁等待超时)还是 1213(死锁)、哪个 operation,一张图锁定嫌疑人。

场景 2 · 抓「发布窗口外」的野重启

发布日的重启是计划内;没人发版时容器自己重启,就是内存超限或崩溃的前兆。

# 每个 service 近 1h 重启几次(k5 口径的 changes 一半)
count by (service) (changes(container_start_time_seconds[1h]))

# 判读:发布窗口内 >0 属预期;
#       窗口外 >0 = 计划外重启,立刻去 k1/k2 看内存是不是顶到限额

场景 3 · OOM 事件计数:枪响了没有

容器被内核枪毙会留下 OOM 事件计数器,用 increase 数「近 1h 开了几枪」。

# 近 1h OOM 事件数(k5 口径的 increase 一半)
sum by (service) (increase(container_oom_events_total[1h]))

# 判读:出现即排查——对照 rag 有前科(LibreOffice 大文件)
#       同时看 m128 的 RSS 有没有冲高不回落

场景 4 · 周报口径:昨天总共跑了多少量

rate 是速度进不了周报,increase 才是「总量」语言。窗口与报表周期对齐即可。

# 昨天一整天总共处理多少请求(总量口径)
sum(increase(siliang_http_requests_total{job="siliang_backend_worker"}[24h]))

# 注意:大窗口 increase 是近似值(受重启归零影响),
#       对内汇报足够;对外财报请以账单系统为准

⚠️ 常见坑 pitfalls

坑 1 · 把 increase 曲线当速率看 — 症状:increase(x[1h]) 的曲线一路缓慢爬升,以为「冲突越来越多」慌了。原因:每个时刻的值都是「往前看 1 小时的总量」,平稳流量下本来就会缓慢上移。正解:看节奏用 rate,看「窗口内发生过没」才用 increase。
# 错: 把 increase(x[1h]) 缓慢爬升当成事故   # → 这是常态
# 对: rate(siliang_db_lock_errors_total[5m])  # → 看即时节奏
坑 2 · 对真正的 Gauge 求 increase — 症状:increase(内存[1h]) 输出忽正忽负的怪数。原因:内存可上可下,不是「只涨」的计数器,涨跌相抵后毫无意义。正解:Gauge 看变化速度用 deriv(第 6 课)。
# 错: increase(siliang_process_resident_memory_bytes[1h])  # → 乱数
# 对: deriv(siliang_process_resident_memory_bytes[1h])     # → 斜率
坑 3 · 以为 changes 能算涨幅 — 症状:用 changes 估算「大概涨了多少」,数字完全对不上。原因:changes 只数次数,涨 0.001 和涨 100 万都记 1 次。正解:问幅度用 increase,问次数用 changes,别串门。
# 错: changes(x[1h]) 当增量用             # → 只数手数
# 对: increase(x[1h]) 看总量               # → 才是涨幅
坑 4 · 给 container_start_time_seconds 求 increase — 症状:重启次数算出来是天文数字或负数。原因:它不是计数器,是「本次启动时间戳」这个状态值,increase 对它没有定义。正解:数状态值的变动用 changes。
# 错: increase(container_start_time_seconds[1h])  # → 无定义
# 对: changes(container_start_time_seconds[1h])   # → 重启次数
坑 5 · 哨兵阈值随手设很大 — 症状:锁冲突哨兵阈值设成 100,复发初期毫无动静,等响时已经攒了一大波。原因:哨兵语义是「非零即事发」,不是「高了才危险」。正解:照 c34 口径:<1 绿 / ≥1 黄 / ≥10 红,发生 1 次就值得看。
# 错: increase(...[1h]) > 100 才报        # → 复发早期全漏
# 对: increase(...[1h]) > 0 即报(哨兵语义,口径以 c34 为准)
坑 6 · 忘了 job 过滤,哨兵天天狼来了 — 症状:自建的锁冲突哨兵总是非零,官方面板却是 0。原因:没排除 server job,双计出的「冲突」是幻觉。正解:照抄 c34 口径带 job 过滤再 sum。
# 错: sum(increase(siliang_db_lock_errors_total[1h]))                      # → 双计
# 对: sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h]))

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

Q1 · increase 和 rate 是什么关系?什么时候用哪个?

参考答案:同一事实的两种问法,increase ≈ rate × 窗口秒数。rate 回答「平均每秒多快」,适合画曲线看节奏、算占比(错误率);increase 回答「窗口内总共涨了多少」,适合大数字面板和哨兵——比如 c34 的「近 1h 锁冲突几次」。

Q2 · changes(container_start_time_seconds[1h]) 为什么能当「重启次数」用?

参考答案:这个指标的值是「容器本次启动的时间戳」,正常运行时是一条死平线;容器每重启一次,时间戳就跳成一个新值——值变了一次手。changes 数的就是「窗口内值变了几次」,所以它数出的跳变次数正好等于重启次数。这是把「死值」变「活数」的通用套路。

Q3 · 锁冲突哨兵为什么用 increase 而不是 rate?

参考答案:哨兵关心的是「最近这段时间发生过没有」,不是「现在每秒几条」。increase(x[1h]) 把一小时内的发生次数加总,哪怕只在某一秒集中爆发了 1 次也会被算进去——非 0 即事发。rate 是平均后的速度,零星几次除以 3600 秒后趋近 0,反而看不见。

Q4 · increase 能用在内存 RSS 上吗?为什么?

参考答案:不能。RSS 是 Gauge(可上可下),不是「只增不减」的计数器——increase 的语义建立在「值只会变大」上,对忽涨忽跌的数没有定义,算出来忽正忽负没法解读。想知道内存「每小时涨多少」,用 deriv 算斜率(第 6 课),内存告警 deriv > 5MB/h 就是这么来的。

← 上一课:rate():从里程表算出速度 📚 课程目录 下一课:P50/P95/P99:把所有请求拉出来排队 →