里程表的两个衍生问题:increase 问「这小时总共涨了多少」(总量),changes 问「值变了几次手」(次数)。覆盖 c34 锁冲突哨兵 / k5 容器重启等「数次数」面板的原理课。
面对一个只涨不跌的里程表,人类只问两个问题:「这小时总共跑了几公里?」——这是 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 |
sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h])):健康时应恒为 0(L-21 修复干净),这数字应与 c34 大数字一致。sum(rate(...)) by (operation, code):这就是 c35 的口径;体会「increase 看总量、rate 看速度」是同一事实的两种问法。count by (service) (changes(container_start_time_seconds[1h])):每个 service 近 1h 重启几次,发布窗口外应为 0。changes(container_start_time_seconds[1h]) + increase(container_oom_events_total[1h]),本课两个函数同框出演。# 近 1 小时总共发生几次锁冲突(c34 口径) sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h]))
# 数变动次数(不管变成多少) changes(container_start_time_seconds[1h]) # 跳 2 次 = 2;哪怕其中一次只差 1 毫秒的时间戳
container_start_time_seconds 不是计数器,是「本次启动的时间戳」——画绝对值是一条死平线。但每次重启它必跳一次,所以数跳变 = 数重启。这是一种通用套路:死数变活数。
# 时间戳(死值)→ 重启次数(活数) count by (service) (changes(container_start_time_seconds[1h]))
# 同一事实,两种问法 rate(x[1h]) # 平均每秒 0.05 次 increase(x[1h]) # 这一小时总共约 180 次
# 哨兵告警思路:非 0 即报(阈值口径以 c34 卡为准) sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h])) > 0
# 今天 0 点以来总共几次(口径示例) sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[12h]))
# 标准姿势:过滤 → increase → sum sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h]))
① 数据库锁冲突(近 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 用法的官方出处:📖 容器盘口径说明(先读我)↗
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,一张图锁定嫌疑人。
发布日的重启是计划内;没人发版时容器自己重启,就是内存超限或崩溃的前兆。
# 每个 service 近 1h 重启几次(k5 口径的 changes 一半) count by (service) (changes(container_start_time_seconds[1h])) # 判读:发布窗口内 >0 属预期; # 窗口外 >0 = 计划外重启,立刻去 k1/k2 看内存是不是顶到限额
容器被内核枪毙会留下 OOM 事件计数器,用 increase 数「近 1h 开了几枪」。
# 近 1h OOM 事件数(k5 口径的 increase 一半) sum by (service) (increase(container_oom_events_total[1h])) # 判读:出现即排查——对照 rag 有前科(LibreOffice 大文件) # 同时看 m128 的 RSS 有没有冲高不回落
rate 是速度进不了周报,increase 才是「总量」语言。窗口与报表周期对齐即可。
# 昨天一整天总共处理多少请求(总量口径) sum(increase(siliang_http_requests_total{job="siliang_backend_worker"}[24h])) # 注意:大窗口 increase 是近似值(受重启归零影响), # 对内汇报足够;对外财报请以账单系统为准
# 错: 把 increase(x[1h]) 缓慢爬升当成事故 # → 这是常态 # 对: rate(siliang_db_lock_errors_total[5m]) # → 看即时节奏
increase(内存[1h]) 输出忽正忽负的怪数。原因:内存可上可下,不是「只涨」的计数器,涨跌相抵后毫无意义。正解:Gauge 看变化速度用 deriv(第 6 课)。
# 错: increase(siliang_process_resident_memory_bytes[1h]) # → 乱数 # 对: deriv(siliang_process_resident_memory_bytes[1h]) # → 斜率
# 错: changes(x[1h]) 当增量用 # → 只数手数 # 对: increase(x[1h]) 看总量 # → 才是涨幅
# 错: increase(container_start_time_seconds[1h]) # → 无定义 # 对: changes(container_start_time_seconds[1h]) # → 重启次数
# 错: increase(...[1h]) > 100 才报 # → 复发早期全漏 # 对: increase(...[1h]) > 0 即报(哨兵语义,口径以 c34 为准)
# 错: sum(increase(siliang_db_lock_errors_total[1h])) # → 双计 # 对: sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h]))
参考答案:同一事实的两种问法,increase ≈ rate × 窗口秒数。rate 回答「平均每秒多快」,适合画曲线看节奏、算占比(错误率);increase 回答「窗口内总共涨了多少」,适合大数字面板和哨兵——比如 c34 的「近 1h 锁冲突几次」。
参考答案:这个指标的值是「容器本次启动的时间戳」,正常运行时是一条死平线;容器每重启一次,时间戳就跳成一个新值——值变了一次手。changes 数的就是「窗口内值变了几次」,所以它数出的跳变次数正好等于重启次数。这是把「死值」变「活数」的通用套路。
参考答案:哨兵关心的是「最近这段时间发生过没有」,不是「现在每秒几条」。increase(x[1h]) 把一小时内的发生次数加总,哪怕只在某一秒集中爆发了 1 次也会被算进去——非 0 即事发。rate 是平均后的速度,零星几次除以 3600 秒后趋近 0,反而看不见。
参考答案:不能。RSS 是 Gauge(可上可下),不是「只增不减」的计数器——increase 的语义建立在「值只会变大」上,对忽涨忽跌的数没有定义,算出来忽正忽负没法解读。想知道内存「每小时涨多少」,用 deriv 算斜率(第 6 课),内存告警 deriv > 5MB/h 就是这么来的。