任务只进不出就是泄漏——后厨未出餐的订单、Redis 里的租约、跨实例的收尸。c7 是整个仪表盘最重要的一个数。覆盖核心盘 🔴 组 5 张面板(c7–c11)· 后端核心仪表盘 siliang-backend。
把后台生成任务想成后厨订单:用户点单(任务创建)→ 灶上炒菜(处理中)→ 出餐(completed)或着火(exception)。c7 的大数字就是"后厨此刻有多少张未出餐的订单"——它是 Gauge,健康时围绕一个低位波动(进多少出多少),只进不出就是泄漏。这些任务曾经真的只进不出、把内存吃爆,所以 c7 被称为内存泄漏核心哨兵:修复之后,它就是防复发的警报器。
多台机器抢着干活,怎么知道"这单归谁、那人死没死"?答案是租约(Redis 里的心跳+遗嘱)和收尸(实例崩了别人替它收尾)。c10 盯租约机制本身坏没坏,c11 盯有没有实例在异常退出——它们长期为 0 才是健康。
先看后厨本厨。每一单从下锅到出餐都要占一个灶位、占一套锅碗——这就是"任务攥着上下文内存"。生意好的时候,后厨里同时有十几张单子在炒,单子数上下波动,但进多少、出多少,收盘时灶位清空。有一天出了个 bug:出的菜没被记账,订单永远显示"处理中"——单子越积越多,灶位全满,最后整个后厨(worker 内存)被撑爆。这就是历史事故:"这些任务曾经只进不出,把内存吃爆"。修好之后,c7 这张"未出餐订单数"就成了哨兵:数字再爬起来,就是复发。
再看着火的单子。厨房着火(exception)的单子不能端给客人,要倒掉重做——而重做会再占一遍灶位。所以 c9 里 exception 比例高,不只是"失败率高",还会连累内存:失败重试会反复占内存,和 RSS 曲线对时间戳能看出联动。
最后是多店协作。连锁后厨(4 个 worker 多实例)怎么分单?每张单在 Redis 里立一张租约——像共享单车扫码开锁:"这单归你 30 秒,到点前必须续约报平安;你不续,系统默认你死了,把车(任务)收回来给别人"。续约失败的原因(reason 标签)直接指向病灶:Redis 挂了(redis_error)、租约逻辑写错了(missing_lease)、清理逻辑异常(lease_expired_failed)。而如果一家分店半夜塌了(实例崩了、租约过期),它手里的烂尾单不能永远悬着——由别的分店替它收尾,这就是"收尸"(c11)。偶尔收一次是常态,经常收 = 公司在批量倒闭(实例在异常退出)。
| 类比里的东西 | 系统里对应的东西 |
|---|---|
| 后厨未出餐的订单数 | c7 活跃生成任务数(Gauge):稳态个位数~几十,<20 绿 / 20–50 黄 / >50 红 |
| 订单只进不出 | 内存泄漏复发:c7 持续单调增长,历史教训是"把内存吃爆",所以它是复发哨兵 |
| 出餐 vs 厨房着火 | c9 结局分布:outcome=completed / exception,配 rate 按 outcome 拆开 |
| 共享单车扫码续费 | c10 租约:谁活着谁续约;续约失败按 reason 拆(redis_error / missing_lease / lease_expired_failed) |
| 同事离职别人收尾 | c11 跨进程收尸:实例崩了/租约过期后,别的实例替它把烂尾任务收尾 |
| 大数字是照片,录像才见方向 | c7 给"现在多少",c8 曲线给"往哪走"——判泄漏只认曲线形态(锯齿 vs 斜线) |
# 对:Gauge 直接读(c7 口径) sum(siliang_chat_resume_active_generation_tasks{job="siliang_backend_worker"})
# 同一个数的曲线版(c8 口径),看 6h 形态 sum(siliang_chat_resume_active_generation_tasks{job="siliang_backend_worker"})
# 复发判据:曲线只涨不跌 + 内存盘 RSS 线性爬升互证 deriv(siliang_process_resident_memory_bytes[1h]) # >5MB/h = 真累积
# Counter 配 rate(c9 口径) sum(rate(siliang_chat_resume_generation_task_outcome_total[5m])) by (outcome)
# 续约失败 + 过期清理失败,按 reason 拆(c10 口径)
rate(siliang_chat_resume_lease_refresh_failed_total[5m]) by (reason)
+ rate(siliang_chat_resume_lease_expired_failed_total[5m])# reason 读法:先分清"环境坏了"还是"代码坏了" rate(...lease_refresh_failed_total[5m]) by (reason) # 健康时长期为 0
# 收尸计数器(c11 口径) sum(rate(siliang_chat_resume_stale_finalized_total[5m])) by (reason, status)
# 不带过滤 = 混入双计的 server job {job="siliang_backend_worker"} # 唯一可信口径
它是什么:后厨此刻有多少张"未出餐的订单"——所有 worker 上的活跃后台生成任务总数(Gauge)。每张单都攥着一份上下文内存,这个数决定了后厨占多大的灶面。历史教训:这些任务曾经只进不出,把内存吃爆——所以它不是普通监控,是泄漏修复后的复发哨兵。
回答什么问题:泄漏修复后有没有复发?这是本仪表盘最重要的一个数。
✅ 稳态个位数~几十;<20 绿 / 20–50 黄 / >50 红
🚨 持续单调增长 = 任务只进不出 = 泄漏复发。动作:立刻去 c8 趋势图确认形态,再对照内存仪表盘 RSS(m101)互证。
# 面板 PromQL(照抄主图谱口径) sum(siliang_chat_resume_active_generation_tasks{job="siliang_backend_worker"})
它是什么:同一个数的曲线版。大数字(c7)是照片,只告诉你"现在多少";曲线才是录像,告诉你"它在往哪走"。判泄漏的关键证据全在这张图的形态里。
回答什么问题:任务是正常周转(有进有出),还是只进不出(泄漏复发)?
✅ 健康曲线围绕某个值上下波动(锯齿)
🚨 只涨不跌的斜线 = 泄漏。动作:对照内存仪表盘 RSS 形态互证——两条线同时爬升就是实锤,立刻按生产实战的 SOP 下钻。
# 面板 PromQL(照抄主图谱口径,看 6h 形态) sum(siliang_chat_resume_active_generation_tasks{job="siliang_backend_worker"})
联动:主图谱 c8 ↗ · 相关课程:泄漏判定心法(锯齿/爬升/平台期) · 内存全局水位
它是什么:后厨订单的结局分布——正常出餐(completed)vs 厨房着火(exception)。结局是 Counter(_total 只增不减),配 rate 按 outcome 拆开,就是"每秒出几单、着几把火"的实时速率。
回答什么问题:后台任务失败率高不高?
✅ 稳态以 completed 为主,exception 偶发成刺
🚨 exception 比例高 = 频繁失败,翻日志。别忘了连锁反应:失败重试还会推高内存——把 exception 速率和 RSS 曲线对时间戳,常能看到联动。
# 面板 PromQL(照抄主图谱口径) sum(rate(siliang_chat_resume_generation_task_outcome_total[5m])) by (outcome)
联动:主图谱 c9 ↗ · 相关课程:rate():算速度 · 结束原因:活干完了没
它是什么:分布式系统的"心跳+遗嘱"体检表。每个后台任务在 Redis 里立了租约(谁活着谁续约);这张图统计两件事的失败速率:续约失败(lease_refresh_failed)和过期任务清理失败(lease_expired_failed),并按 reason 拆开病灶。
回答什么问题:多实例协作机制本身有没有坏?
✅ 长期为 0
🚨 redis_error 高 = Redis 不稳;missing_lease 高 = 租约逻辑 bug;lease_expired_failed 高 = 清理逻辑异常。动作:先按 reason 分诊——环境问题查 Redis,逻辑问题提代码 bug 单。
# 面板 PromQL(照抄主图谱口径)
rate(siliang_chat_resume_lease_refresh_failed_total[5m]) by (reason)
+ rate(siliang_chat_resume_lease_expired_failed_total[5m])
联动:主图谱 c10 ↗ · 相关课程:连接池台账(Redis 池视角) · 视频收割器(同类机制)
它是什么:"收尸计数器"——某个实例崩了/租约过期后,由别的实例替它把烂尾任务收尾的次数。它是租约机制的兜底证据:有收尸,说明真的有实例死过。
回答什么问题:有没有实例在异常退出?
✅ 偶发为 0;active 稳定而 stale 高 = 有实例异常退出(对照发布记录)
🚨 stale 持续走高 = 实例在批量异常退出。动作:对照发布时间轴——发布窗口内属预期滚动;窗口外持续出现 = OOM/崩溃排查(转容器层 k5 与内存盘)。
# 面板 PromQL(照抄主图谱口径) sum(rate(siliang_chat_resume_stale_finalized_total[5m])) by (reason, status)
联动:主图谱 c11 ↗ · 相关课程:容器层:盒子视角 · OOM kill:内存超限直接枪毙
阈值只来自核心盘口径卡:<20 绿 / 20–50 黄 / >50 红。黄区给缓冲,红区立刻响。
# 告警规则:后台任务积压(阈值来自核心盘 c7 口径卡) groups: - name: siliang-bg-tasks rules: - alert: BackgroundGenerationTasksWarning expr: sum(siliang_chat_resume_active_generation_tasks{job="siliang_backend_worker"}) > 20 for: 10m # 黄区持续 10 分钟才报,避开正常高峰的抖动 labels: severity: warning - alert: BackgroundGenerationTasksCritical expr: sum(siliang_chat_resume_active_generation_tasks{job="siliang_backend_worker"}) > 50 for: 5m # 红区:泄漏复发嫌疑,立刻按 SOP 处理 labels: severity: critical
判读收尾:告警触发后第一步永远是打开 c8 看形态,而不是先重启。
从哨兵到实锤:先定性(形态),再量化(斜率),最后找同伙(协程)。
# 第 1 步:确认形态——大数字只给现在,曲线才给方向(c8 口径) sum(siliang_chat_resume_active_generation_tasks{job="siliang_backend_worker"}) # 第 2 步:对照内存仪表盘 RSS 形态互证(m101 口径,锯齿/爬升/平台期) max_over_time(siliang_process_resident_memory_bytes[1m]) # 第 3 步:量化爬升速度(内存盘核心告警口径:deriv > 5MB/h = 真累积) deriv(siliang_process_resident_memory_bytes[1h]) # 第 4 步:任务不是唯一嫌疑人——顺手排除协程泄漏(m103 口径) max_over_time(siliang_asyncio_tasks_active[1m]) # 判读:任务斜线 + RSS 爬升 = 实锤复发;任务稳而 RSS 爬 = 换嫌疑人再查
c10 冒头先别慌,reason 标签会告诉你该修环境还是修代码。
# 续约失败 + 过期清理失败,按 reason 拆(c10 口径照抄) rate(siliang_chat_resume_lease_refresh_failed_total[5m]) by (reason) + rate(siliang_chat_resume_lease_expired_failed_total[5m]) # reason 读法: # redis_error → Redis 不稳:先查 Redis 本身(池面板 m117/m118 互证) # missing_lease → 租约逻辑 bug:提代码 bug 单 # lease_expired_failed → 清理逻辑异常:查收尾代码 # 健康口径:两条线长期为 0;偶发成刺可容忍,持续非零必须处理
滚动发布必然换实例;收尸计数在发布窗口内偶发属预期,窗口外持续出现才是事故。
# 发布后 10 分钟看收尸计数(主图谱 #triage「发布后确认健康」口径) sum(rate(siliang_chat_resume_stale_finalized_total[5m])) by (reason, status) # 判读:active 稳定 + stale 偶发 = 旧实例干净退场 # stale 持续走高 = 有实例异常退出 → 查容器重启/OOM(k5)与 RSS(m101) # 配套确认:HTTP 在途发布后逐 worker 归零(c18 ops 排空口径) sum(siliang_http_requests_in_progress) by (method) + sum(siliang_ops_http_in_flight)
exception 高不只是"失败率高"——着火的单要重做,重做还会再占一遍内存。
# 结局分布(c9 口径):着火速率 sum(rate(siliang_chat_resume_generation_task_outcome_total[5m])) by (outcome) # 连锁检查:失败重试反复占内存——RSS 形态对时间戳 max_over_time(siliang_process_resident_memory_bytes[1m]) # 判读:exception 速率高的时段若 RSS 同步冲高 = 重试在放大内存压力 # 处置:先修失败根因(日志),必要时限流重试,而不是扩内存
# 错: 只盯 c7 当前值,低于 50 就安心 # 对: 看 c8 的 6h 形态——只涨不跌的斜线 = 泄漏
# 错: 曲线涨了两格就喊泄漏 # 对: 拉 6h 看整体形态:锯齿 vs 单调爬升,再定罪
# 错: rate(siliang_chat_resume_active_generation_tasks[5m]) # 对: deriv(siliang_chat_resume_active_generation_tasks[1h]) # → 每小时涨多少
# 错: 看到 c10 非零就去重启 Redis # 对: rate(...failed_total[5m]) by (reason) # → 先分诊再动手
# 错: stale 高 → 扩容 # 对: changes(container_start_time_seconds[1h]) # → 谁在重启(k5 口径)
# 错: 追求 active 恒等于 0 # 对: 锚点:稳态个位数~几十;<20 绿 / 20–50 黄 / >50 红
# 错: 只看 c9 的失败率百分比 # 对: c9 exception 速率 × max_over_time(rss[1m]) 对时间戳互证
# 错: sum(siliang_chat_resume_active_generation_tasks) # 对: sum(siliang_chat_resume_active_generation_tasks{job="siliang_backend_worker"})
参考答案:因为后台生成任务攥着上下文内存,历史上真的发生过"任务只进不出、把内存吃爆"的事故。修复之后,c7 就是防复发的哨兵:稳态个位数~几十,持续单调增长 = 泄漏复发。别的图告诉你"现在舒不舒服",这张图告诉你"上次的绝症有没有复发"。
参考答案:多台机器分单时,一台崩了它手里的任务不能永远悬着。租约=带过期时间的许可证:"这单归你 30 秒,到点前续约报平安;没续上默认你死了,任务转给别人。"续约失败按 reason 分诊:redis_error=Redis 不稳(环境问题);missing_lease=租约逻辑 bug(代码问题);lease_expired_failed=清理逻辑异常(代码问题)。
参考答案:只涨不跌的斜线(持续单调增长)——任务是只进不出的,泄漏复发。下一步对照内存仪表盘的 RSS 形态(m101):两条线同时爬升就是实锤;再用 deriv(RSS[1h]) 量化爬升速度(内存盘核心告警口径大于 5MB/h = 真累积),并顺手排除 asyncio 协程泄漏(m103)。
参考答案:实例崩了或租约过期后,由别的实例替它把烂尾任务收尾,这个次数就是收尸计数(c11)。偶发为 0 到偶发非零都正常;active 稳定而 stale 持续走高 = 有实例在异常退出——对照发布记录:发布窗口内属滚动预期,窗口外持续出现就要查容器重启与 OOM。