适用场景**:定时同步、批量推送、库存与轨迹更新这类「按周期发生」的后台任务。
核心判断:监控只能看见发生过的事,看不见没发生的事;要补上后者,指标必须与业务结果解耦。
一、四种沉默
一条链路停摆时,面板上的表现往往是「更健康」而不是「更异常」:
| 形态 | 实际发生 | 面板表现 | 可发现指标 |
| 调用没发生 | 调度未触发、实例迁移、任务被关 | 失败数 0、曲线平滑 | 任务心跳 |
| 调用了没产出 | 上游返回 0 条、源表为空 | 成功 0、失败 0 | 零产出周期数 |
| 数据没上报 | 上报进程挂、指标改名、标签变更 | 曲线断线或规则匹配为空 | 数据新鲜度 |
| 消息没送达 | 通道失效、机器人被移除、通知被关 | 告警产生,人没收到 | 通道周测 |
第三种有一处默认值值得单独确认:不少告警引擎对「匹配不到数据」的默认动作是跳过,而不是告警。于是指标改名这件事,会被系统理解为「没有问题」。核心规则上,这个默认值应当改成告警。
二、三个计数器
它们不看业务结果,只看有没有发生。
from datetime import datetime def check_heartbeat(last_run_at, now, period_sec, tolerance=2): if last_run_at is None: return "从未运行过" gap = (now - last_run_at).total_seconds() if gap > period_sec * tolerance: return f"已 {gap / 3600:.1f} 小时未运行" return None def check_freshness(latest_at, now, max_age_sec): age = (now - latest_at).total_seconds() if age > max_age_sec: return f"数据已陈旧 {age / 3600:.1f} 小时" return None def check_zero(success_count, zero_streak, limit=3): if success_count == 0 and zero_streak >= limit: return f"连续 {zero_streak} 个周期零产出" return None
心跳的阈值只与时间有关,与业务量无关,所以不需要按业务波动反复调参——这是它比「失败率」可靠的地方。新鲜度针对数据表,一个「最新写入时间」就能覆盖整条链路的停摆。零产出针对「跑了但没结果」,它填补的正是失败数为 0 的那块空白。
三、规则体检表
规则要能响,依赖四件事同时成立:指标在上报、名字没改、阈值有意义、通道还通。任一项变化,规则就变成摆设。
| 规则名 | 依赖指标 | 最近评估 | 最近触发 | 负责人 |
| 入库同步失败 | sync_fail_total |
今天 | 从未 | 仓配组 |
| 库存未更新 | stock_update_ts |
今天 | 10-14 | 仓配组 |
| 下单错误率 | order_err_ratio |
两天前 | 10-26 | 交易组 |
判读口径三条:评估时间为空说明规则已死;触发时间超 90 天说明规则多余或阈值失配;评估时间停滞说明依赖的指标已经不再更新。
四、验收标准
- 关掉调度:两个周期内应收到「未运行」,而不是安静数天。
- 上游返回空列表:三个周期后应收到「零产出」,即使失败数为 0。
- 改掉指标名一个字符:体检表该行「最近评估」应为空并进入待修列表。
- 移除通知机器人:本周通道周测失败,且失败能通知到第二联系人。
上线检查清单
- 每个定时任务都有与结果解耦的心跳
- 关键表有新鲜度指标
- 零产出是独立告警,不挂在失败数上
- 核心规则的无数据动作配成告警
- 体检表每周生成,90 天未触发进复核
- 通道周测要求人工回执