3. 异常去重与相似度计算

告警风暴的本质往往不是故障数量多,而是同一故障被反复上报。一条数据库连接池耗尽,可能在 30 秒内触发 200 条超时告警、80 条连接拒绝告警、50 条接口 5xx 告警。如果不去重,值班人员面对的是 330 条噪声;去重后,应该只剩 1 条根因事件。本节要解决的核心问题就是:如何判断两条异常“说的是同一件事”,并据此完成告警去重。

去重方法按“判断依据”可分为五类,它们不是互斥的,而是从粗到细、从快到慢的递进关系。理解每类方法的适用边界,比记住算法名字更重要。

一、基于规则的去重:最快但最脆

规则去重依赖人工定义的字段组合作为唯一键。典型做法是取 service + alert_name + instance 作为去重键,在时间窗口内只保留首条。例如:

dedup_key = f"{alert.service}|{alert.rule_id}|{alert.labels['instance']}"
window = 300  # 秒
if dedup_key in seen and now - seen[dedup_key] < window:
    suppress(alert)
else:
    seen[dedup_key] = now
    emit(alert)

规则法的优点是零计算成本、结果可解释、便于审计;缺点是键一旦选错就会误合并或漏合并。比如把 instance 换成 pod_name,滚动发布时同一故障会因 Pod 名变化而被判为不同事件。规则法适合结构稳定、字段语义明确的告警源,如主机 CPU、磁盘使用率。

二、指纹去重:把“模板”变成稳定标识

真实告警文本常带变量:Timeout connecting to 10.2.3.4:3306 after 3000ms。指纹法的思路是先做模板化,把变量替换为占位符,再对模板取哈希。常见替换规则包括:IP、端口、数字、UUID、路径、时间戳。处理后得到 Timeout connecting to <IP>:<PORT> after <NUM>ms,再计算 MD5 或 SHA1 作为指纹。

指纹法比规则法更抗变量扰动,适合日志型异常。但它对模板质量高度敏感:如果正则漏掉了某个变量,同一故障会生成多个指纹;如果正则过度贪婪,不同故障又会被压成同一指纹。实践中建议把模板规则纳入版本管理,并定期用历史数据回放验证指纹数量是否稳定。

三、编辑距离与集合相似度:处理“措辞不同但意思相近”

当两条告警既没有相同指纹,字段也不完全一致时,需要计算文本相似度。编辑距离(Levenshtein)衡量把字符串 A 变成 B 所需的最少增删改次数,归一化后得到 0~1 的相似度。例如 connection refused 与 connection reset 的编辑距离为 5,归一化相似度约 0.72。

编辑距离对顺序敏感、计算量为 O(m×n),长文本上开销较大。此时可改用集合相似度:把文本切分为词或 shingle 集合,用 Jaccard 系数 |A∩B| / |A∪B| 衡量重合度。集合法忽略词序,对“连接被拒绝”和“拒绝连接”这类换序表达更稳健,且可用 MinHash 近似加速。

四、向量嵌入:语义层面的相似

编辑距离和集合相似度都停留在字面层。数据库连接超时 与 DB connection timeout 字面重合度低,但语义几乎相同。向量嵌入把文本映射为稠密向量,用余弦相似度衡量语义距离。工程上常用轻量句向量模型或领域微调模型,把告警文本编码为 256~768 维向量,再通过近似最近邻(如 HNSW)在毫秒级检索出历史相似事件。

向量法的阈值需要按业务校准:阈值过高会漏合并,过低会把不同故障混为一谈。建议先用一批已标注的“同事件/不同事件”样本画出相似度分布,取使 F1 最大的分位点作为初始阈值,再随线上反馈迭代。

五、方法选择与组合策略

五类方法并非单选。一个可落地的去重流水线通常是:先用规则键做快速分流,再用指纹合并模板相同的告警,剩余未命中的进入集合相似度或向量相似度做二次判定。这样既控制了计算量,又保留了语义合并能力。

告警去重流水线:五类相似度方法的递进与组合 原始告警流 330 条 / 30 秒 ① 规则去重 service+rule+instance ② 指纹去重 模板化 + 哈希 ③ 编辑距离/集合 Levenshtein / Jaccard ④ 向量嵌入 余弦相似度 + HNSW 去重后事件 1 条根因事件 未命中 → 下一级 关键参数与判断依据(示例值,需按业务校准) 规则窗口 window = 300s 键:service+rule+instance 误合并风险:键过粗 指纹模板 替换 IP/端口/数字/UUID MD5 取前 16 位 漏替换 → 指纹膨胀 相似度阈值 Jaccard ≥ 0.75 Levenshtein 归一化 ≥ 0.80 按 F1 最大分位点选取 向量检索 维度 256~768 余弦 ≥ 0.88,TopK=5 HNSW 毫秒级召回

六、告警去重实战任务

下面给出一个可直接落地的去重任务,读者可据此在真实告警数据上复现。

任务目标:对某服务 30 秒窗口内的 330 条告警去重,输出不超过 5 条事件,并给出每条事件的合并依据。

输入数据(示例):每条告警包含 timestamp、service、rule_id、instance、message、severity 六个字段。示例数据中,order-service 的 DB_TIMEOUT 规则在 3 个实例上各触发约 40 次,message 中仅 IP 和耗时数字不同。

处理过程:

  1. 按 service + rule_id + instance 分组,300 秒窗口内每组保留首条,330 条降至 9 条。
  2. 对 9 条消息做模板化,替换 IP、端口、数字,得到 3 个不同指纹,9 条降至 3 条。
  3. 对剩余 3 条计算两两 Jaccard 相似度,若 ≥ 0.75 则合并。示例中两条 DB_TIMEOUT 消息相似度为 0.82,合并为 1 条;另一条 CONN_REFUSED 相似度 0.31,保留。
  4. 最终输出 2 条事件,分别标注合并数量与代表消息。

结果判读:去重率 = (330 − 2) / 330 ≈ 99.4%。但去重率不是唯一指标,还要看误合并率:随机抽取 20 条被合并的告警,人工确认是否属于同一根因。若误合并率超过 5%,说明阈值过松或规则键过粗,应回退调整。此外,需检查是否有“本应合并却未合并”的漏合并,通常表现为同一根因在输出中仍出现多条。

工程建议:把去重键、模板正则、相似度阈值都做成可配置项,并记录每次合并的“依据链”(命中哪一级、相似度多少)。这样当值班人员质疑“为什么这两条被合并了”时,可以直接回溯判断过程,而不是只能回答“算法算的”。
常见误区:直接用向量相似度对全量告警两两计算。330 条告警两两比较是 5 万多次向量运算,在告警风暴中会拖垮去重服务。正确做法是先用规则和指纹把候选集压到个位数,再对少量候选做向量比对。