结论先行
现有回答把「回放机制」讲透了,但影子模式只给了一个时间盒(2 周),没给工程实现和量化验收判据——这两块才是「该报没报 / 不该报乱报」能不能被证伪的关键。我补三件事,都能今天落:
- 影子模式的正确姿势不是"跑两周看误报",而是"新规则产生一条可与老规则 diff 的 shadow 序列";
- 回放通过/不通过必须有 3 个硬指标(trigger_count / min_margin / fp_ratio),没有硬指标的回放等于"跑了一下没报错";
- 动态基线类(z-score / 同环比)离线回放不可行,但可以在线双跑——Prometheus 里加
alert: false+ label 静默,72h 后拉 shadow 序列 diff 老规则。
一、影子模式:工程落地(不是"跑两周",是"双跑 + diff")
现有回答说「动态基线类走影子模式 + 2 周观察期」,但没说怎么让新规则跑但不通知。给一个具体实现:
做法 A:Prometheus 侧双跑(推荐,零额外组件)
# 老规则(照常 alert)
- group: prod/redis
rules:
- alert: RedisSlowQuery
expr: histogram_quantile(0.99, sum(rate(redis_slowlog_duration_seconds_bucket[5m])) by (le)) > 50
for: 3m
labels:
severity: P2
shadow: "false"
# 新规则(同一个指标,改阈值 / 改 for,alert 关掉)
- group: shadow/redis
rules:
- alert: RedisSlowQuery_v2
expr: histogram_quantile(0.99, sum(rate(redis_slowlog_duration_seconds_bucket[5m])) by (le)) > 50
for: 2m # 把 3m 改成 2m
labels:
severity: P2
shadow: "true" # ← 关键标签
Alertmanager 侧加一条静默(只静默 shadow,不影响老规则):
# alertmanager.yml
silences:
- matchers: [shadow="true"]
comment: "shadow rule, do not notify"
注意:
silences是持久化的,重启不丢。如果你不想在 alertmanager 里加配置,更干净的做法是 Prometheus 侧直接alert: false——但这样 shadow 规则就不会进 alertmanager 的任何面板,没法事后审计。所以推荐「alert: true+ shadow label + 静默」的组合。
做法 B:离线 shadow evaluator(适合规则多、CI 门禁场景)
写一个 200 行左右的 Go / Python 服务,定时(比如每 5min)拉 query_range,本地跑所有 shadow: "true" 规则的触发判定,结果落一张 shadow_alerts 表(Postgres / ClickHouse 都行):
shadow_alerts(
rule_name VARCHAR,
triggered_at TIMESTAMP,
window_start TIMESTAMP,
window_end TIMESTAMP,
duration_s INT,
margin FLOAT, -- 离阈值最近的差值(关键!)
old_rule_fired BOOLEAN -- 同一时间窗老规则是否也触发了
)
margin 是回放报告里最值钱的字段:它告诉你「这条规则离触发还差多少」。你那个「Redis 慢查询 52ms 抖了 2.5min 差 30s 没够 3min」的事故,回放报告里就会有一条 margin = 0.30min, duration_s = 150——这个 margin 分布就是阈值调优的直接依据,比"触发 0 次"有用 10 倍。
72h 验收判据(可量化,不是"看两周")
影子期跑 72h 后,拉 shadow_alerts 做 diff:
| 判据 | 通过 | 不通过(回炉改阈值) |
|---|---|---|
| trigger_count | 1 ≤ n ≤ 3 | n = 0(规则永远不响,废的)或 n > 5(太松,会淹) |
| min_margin(离阈值最近的差值) | margin ≥ 20% 阈值 | margin < 5%(阈值贴着毛刺,迟早误报) |
| fp_ratio(触发中无对应事故/工单的占比) | ≤ 0.33 | > 0.5(一半是误报,该改) |
| 老规则 vs 新规则触发窗口 diff | 新规则触发窗口 ⊆ 老规则触发窗口(或老规则没响、新规则响了 = 真增量) | 新规则触发窗口远大于老规则 = 阈值太松 |
trigger_count = 0 必须 block——这是最容易忽略的:一条 90 天回放 0 次触发的规则,上线后就是「该报没报」的温床。你那个「52ms 差 30s」的事故,回放 0 次触发 + margin = 0.30min,reviewer 一眼就能看出 for 条件太长。
二、回放报告的 3 个必检字段(CI 门禁)
回放脚本输出 JSON,PR 里必须带这个文件。reviewer 只看 3 个字段:
{
"rule": "redis_slow_query_v2",
"window_days": 90,
"trigger_count": 2,
"min_margin_ratio": 0.30,
"fp_ratio": 0.0,
"windows": [
{"start": "…", "end": "…", "duration_s": 210, "margin_s": 30},
{"start": "…", "end": "…", "duration_s": 180, "margin_s": 15}
]
}
trigger_count = 0→ CI 直接红,PR 不能合(规则无效)min_margin_ratio < 0.05→ CI 黄,reviewer 必须手动确认(阈值贴着毛刺)fp_ratio > 0.5→ CI 红(一半误报,该改阈值或加for)
这 3 个字段覆盖了「该报没报」(trigger_count = 0)和「不该报乱报」(fp_ratio 高)两个方向,比"跑一下没报错"强一个量级。
三、动态基线类(z-score / 同环比)的特殊处理
这类规则离线回放确实不行(依赖当时上下文),但在线双跑完全可行:
- 老规则照常 alert;
- 新规则(改 z-score 的窗口 / 阈值)加
shadow: "true"label,alertmanager 静默; - 72h 后拉
shadow_alerts,diff 老规则和新规则的触发窗口; - 如果新规则在老规则没响的时段响了 → 检查是不是真增量(对应事故)还是误报(凌晨批跑);
- 如果新规则在老规则响了的时段没响 → 漏报,回炉。
一个实操坑:z-score 类规则在凌晨批跑期间天然会"响"(因为 QPS 跌到 0,z-score 炸了)。影子期 72h 必须覆盖一个完整业务周期(白天 + 凌晨 + 批跑窗口),否则你会在凌晨批跑时段看到一堆"误报",但其实那是规则正常工作。建议影子期起始时间对齐到上一个业务周期的起点(比如周一 0 点),72h 后刚好覆盖一个完整周。
四、一句话总结
回放不是"跑一下没报错",是 trigger_count / min_margin / fp_ratio 三个硬指标过 CI 门禁;影子不是"跑两周看误报",是 shadow label + 静默 + 72h diff 老规则;动态基线类离线不行,但在线双跑完全可行。三者组合,你那个「52ms 差 30s」的事故在回放阶段就会被 trigger_count = 1, margin_s = 30, fp_ratio = 0 直接暴露,不用等上线两周后才发现。