半夜18条告警轮番响,真正需要处理的其实只有1件事
凌晨2:17,值班手机连续响了18次:网关P99升高、订单超时、优惠券失败、线程池排队、队列积压、数据库连接上涨……
值班同学花了8分钟逐条点开,才确认它们都源于同一个支付依赖变慢。第19条真正关键的“支付成功率跌破用户承诺”,反而被通知洪水淹没。
告警多不等于观测充分。一个人一次只能处理有限的决策;如果每个症状都独立分页,系统是在把拓扑结构和关联工作外包给半夜的大脑。
本文使用教学支付链路。约束是:重大用户影响要在5分钟内触达;同一事故不能重复轰炸;根因未知时也要能根据用户症状行动;告警恢复要避免来回抖动;低流量时不能被单个失败无限放大。
先区分“需要叫醒人”和“值得记录”
CPU高、队列长、依赖慢都可能有价值,但不一定都需要立即叫醒同一个人。分页信号应该对应可操作的用户承诺,例如支付成功率或关键流程延迟正在快速消耗允许失败的预算。
原因型指标适合帮助定位,也适合在达到确定风险时分页;否则可以进入事故上下文、工单或白天观察。根因告警不能完全替代症状告警,因为未知故障可能没有预设原因规则。
收敛不是简单去重
只按告警名称去重,会把网关和订单看成两件事;只按时间合并,又可能误把两个独立事故塞在一起。更有用的关联键通常结合:用户旅程、受影响区域、共同依赖、版本变更和时间邻近。
下面的代码只演示“同服务、同症状族、5分钟内”收敛的形状,真实系统要结合拓扑和变更信息:
from dataclasses import dataclass
@dataclass(frozen=True)
class Alert:
service: str
symptom: str
at_minute: int
def incidents(alerts, window=5):
groups = []
for alert in sorted(alerts, key=lambda x: x.at_minute):
match = next((g for g in reversed(groups)
if g[0].service == alert.service
and g[0].symptom == alert.symptom
and alert.at_minute - g[-1].at_minute <= window), None)
if match is None:
groups.append([alert])
else:
match.append(alert)
return groups
alerts = [
Alert("checkout", "payment_degraded", 137),
Alert("checkout", "payment_degraded", 139),
Alert("checkout", "payment_degraded", 141),
]
groups = incidents(alerts)
assert len(groups) == 1 and len(groups[0]) == 3
收敛后的事故仍应保留全部原始信号,并在严重度升级、影响范围扩大或责任团队变化时更新通知。去重不是丢数据,而是把多条证据组织成一个可行动对象。
快燃烧与慢燃烧解决不同问题
只看5分钟错误率,能快速发现尖峰,却容易被短暂噪声触发;只看6小时,又会发现得太晚。可以同时使用短窗口和长窗口:短窗口确认正在快速恶化,长窗口证明影响不是一闪而过。
燃烧率表达“当前失败速度相对于可持续速度有多快”。大于1意味着如果这个速度持续,预算会提前耗尽;具体窗口和阈值必须用业务SLO、流量分布和响应目标回放验证,不能照抄别人的数字。
告警也需要测试
发布前至少做四类演练:
-
注入短暂单点错误,确认不会无意义分页; -
持续制造用户失败,确认在目标时间内叫醒正确团队; -
同一依赖扩散多个症状,确认只建立一个主事故; -
恢复后再次抖动,确认通知不会反复开关。
每条分页告警都应有负责人、用户影响、当前证据、第一步动作、看板与止损方式。若值班同学收到后只能问“这条告警是什么意思”,它还不是可操作告警。
复盘时不要只统计告警数量,还要记录:多少页需要立即行动、多少页重复、首次有效信号到首次通知用了多久、第一次动作是否正确,以及被静音的规则有没有漏掉真实事故。
好的告警体系不是让手机更安静,而是让每一次响铃都值得人立刻做一个明确动作。
- 点赞
- 收藏
- 关注作者
评论(0)