本节摘要:8.1 把新版本送上了线,本节给它装一双眼睛。指标集分三层:技术层(延迟 p95/p99、错误率、单请求成本——"系统还活着吗")、语义层(低置信占比、拒答率、负反馈率、忠实度抽检分与裁判抽检分——"回答还有质量吗")、漂移层(第 7.3 节 PSI 矩阵、分桶命中率——"世界变了吗")。三层拼进一页式面板(第 4.4 节离线仪表盘的线上版):顶部红绿灯总览、中部各层趋势、底部下钻入口。告警设计的核心不是阈值本身,而是纪律:每个指标立"正常/观察/告警"三档(阈值表附后),告警分三级响应——提示(看一眼)→ 工单(当天处理)→ 寻呼(立即拉人);每条告警必须链接一页 runbook(8.3 节),否则它只是噪音制造器;防疲劳靠静默窗口、告警去重与季度阈值校准。本节附告警判定脚本:输入指标快照与阈值表,输出分级状态与响应动作——它就是告警系统判定核心的最小实现(MOCK 可跑)。
阅读完本节,你应当能够:
| 层 | 回答的问题 | 代表指标 | 采集来源 | 时滞 |
|---|---|---|---|---|
| 技术层 | 系统还活着吗 | 延迟 p50/p95/p99、HTTP 错误率、超时率、单请求成本(token 分账) | 网关 / trace(6.2) | 分钟级 |
| 语义层 | 回答还有质量吗 | 低置信占比(<0.5)、拒答率、空输出率、负反馈率、抽检忠实度(4.3)、裁判抽检分(5 章) | trace 字段 + 定时抽样任务 | 小时~天级 |
| 漂移层 | 世界变了吗 | PSI 矩阵(7.3)、分桶命中率、场景占比 | 周度体检任务 | 天~周级 |
三层的时滞差决定告警节奏:技术层分钟级盯(分金币给自动告警),语义层小时级看(定时任务 + 日检),漂移层周度议(7.3 周四评审)。别把漂移层配成分钟级告警——PSI 本来就该慢慢看,配成实时告警只会制造疲劳(观点)。
┌────────────────────────────────────────────────────────────┐ │ 顶部:红绿灯总览(每层最坏状态上浮) │ │ 技术层 🟢 │ 语义层 🟡 低置信占比 8.4%(观察档) │ 漂移层 🟢 │ ├────────────────────────────────────────────────────────────┤ │ 中部:核心趋势(7 天窗口,纵轴带基线参考线) │ │ [p95 延迟] [错误率] [低置信占比] [负反馈率] │ │ [每日成本] [PSI 三指标] [裁判抽检分] │ ├────────────────────────────────────────────────────────────┤ │ 底部:下钻入口 │ │ 按场景 × 版本 × 用户群切片 → 6.2 的 trace 检索 │ └────────────────────────────────────────────────────────────┘
两条设计原则沿用第 4.4 节:每格带基线对比(发布前的稳定期数据画成参考线,否则"8.4%"这种绝对数没有意义);同屏分层不折叠(总览坏到哪一层,一眼可辨——面板是给值班的人看的,不是给汇报看的)。
示例阈值(示意数值,以自家稳定期分布校准后填入):
| 指标 | 正常 | 观察(提示) | 告警(工单/寻呼) | 响应级 |
|---|---|---|---|---|
| p95 延迟 | < 3s | 3~5s | > 5s 持续 10min | 寻呼 |
| 错误率 | < 0.5% | 0.5%~2% | > 2% 持续 5min | 寻呼 |
| 低置信占比 | < 6% | 6%~9% | > 9% 持续 1h | 工单 |
| 拒答率 | < 3% | 3%~5% | > 5% | 工单 |
| 负反馈率 | < 10% | 10%~13% | > 13% 持续 1 天 | 工单 |
| 抽检忠实度 | > 0.95 | 0.90~0.95 | < 0.90 | 工单 |
| PSI(任一核心维) | < 0.10 | 0.10~0.25 | > 0.25 | 提示→周四评审 |
三级响应的含义:提示(Slack 频道消息,值班看一眼);工单(当天处理,附 runbook 链接);寻呼(立即拉人,8.3 节应急预案启动)。持续时长条件防抖动——单点越线不算事,持续越线才是事。
# check_alerts.py(纯标准库,可直接运行) """告警判定核心:指标快照 × 阈值表 → 分级状态与响应动作。""" import json # 方向:high = 值越大约糟(延迟/错误率);low = 值越小越糟(忠实度) THRESHOLDS = { "p95_latency_s": {"warn": 3.0, "crit": 5.0, "direction": "high", "page": True}, "error_rate": {"warn": 0.005, "crit": 0.02, "direction": "high", "page": True}, "low_conf_share": {"warn": 0.06, "crit": 0.09, "direction": "high", "page": False}, "reject_rate": {"warn": 0.03, "crit": 0.05, "direction": "high", "page": False}, "faithfulness": {"warn": 0.95, "crit": 0.90, "direction": "low", "page": False}, } def check(metrics: dict, sustained: dict) -> list[dict]: """metrics: 当前值;sustained: 已持续分钟数(防抖)。返回告警列表。""" alerts = [] for name, value in metrics.items(): rule = THRESHOLDS[name] worse = (value > rule["crit"]) if rule["direction"] == "high" \ else (value < rule["crit"]) bad = (value > rule["warn"]) if rule["direction"] == "high" \ else (value < rule["warn"]) if worse and sustained.get(name, 0) >= 10: # 持续才升级 level = "寻呼" if rule["page"] else "工单" alerts.append({"metric": name, "value": value, "level": level}) elif bad: alerts.append({"metric": name, "value": value, "level": "提示"}) return alerts if __name__ == "__main__": snapshot = {"p95_latency_s": 5.8, "error_rate": 0.003, "low_conf_share": 0.084, "reject_rate": 0.02, "faithfulness": 0.97} held = {"p95_latency_s": 25, "low_conf_share": 70} # 已持续分钟(示意) result = check(snapshot, held) print(json.dumps(result, ensure_ascii=False, indent=2))
运行输出(实测,本机 Python 3.12):p95 延迟 5.8s 且已持续 25 分钟 → 升级为"寻呼";低置信占比 8.4% 落观察档 → "提示";错误率 0.3% 正常不出警——判定逻辑就是三张表的查表加防抖,复杂度都在阈值本身。
⚠️ 最常见的失败不是"没告警",而是"告了三个月没人信"。告警系统的信用和裁判一样要靠校准维持:一旦团队学会无视黄色,整套面板就退役了。
告警响了之后呢?"寻呼"级指标往往意味着用户已经在承受劣化——下一节写应急预案:软回退、硬回退与熔断三级手段,以及一页让凌晨三点被叫醒的人照着做就能赢的 runbook。