3、告警去重与降噪:告警聚合策略、告警抑制规则、告警静默机制

说实话,做运维最怕什么?不是没告警,而是告警太多。

我见过最夸张的一次,某个客户的核心业务系统出了点小故障,结果五分钟内涌进来两万多条告警。值班同学直接懵了,根本分不清哪个是根因,哪个是连带反应。这就是典型的「告警风暴」——你想想看,如果每秒钟都在响警报,那跟没响有什么区别?

所以今天咱们聊聊告警去重与降噪。说白了,就是让告警系统变得「聪明」一点,别啥事都往上捅。

3.1 告警聚合策略:把相似的告警「拧」成一股绳

告警聚合,核心思路就一句话:把同一时间、同一维度、同一原因的告警合并成一条。

我个人习惯把聚合策略分成三个层次:

3.1.1 时间窗口聚合

举个例子,某个服务端口抖动,一秒内报了 50 次「连接超时」。如果每条都发出去,那就是灾难。

我的做法是:设置一个 60 秒的时间窗口。窗口内相同类型的告警,只发一条,但计数累加。

# 伪代码示例:时间窗口聚合逻辑
def aggregate_alerts(alert_stream, window_seconds=60):
    cache = {}
    for alert in alert_stream:
        key = (alert.type, alert.host, alert.metric)
        if key in cache and (now() - cache[key].first_seen) < window_seconds:
            cache[key].count += 1
            cache[key].last_seen = now()
        else:
            cache[key] = AlertGroup(first_seen=now(), count=1)
            send_alert(alert)  # 只发送第一条

嗯,这里要注意:窗口大小别设太死。我见过有人设 300 秒,结果一个故障都处理完了,告警才发出来。我个人建议窗口设在 30~120 秒之间,具体看你的业务容忍度。

3.1.2 字段维度聚合

有些告警,虽然内容不同,但根因是同一个。比如「磁盘使用率 95%」和「inode 耗尽」,其实都是磁盘满了。

我在项目中遇到过这种情况:某台数据库服务器磁盘满了,结果同时触发了磁盘告警、慢查询告警、备份失败告警。如果不聚合,值班人员得看三条告警才能拼出全貌。

我的做法是:按主机 + 故障域做聚合。只要告警来自同一台机器,且时间窗口重叠,就合并成一条「故障摘要」。

聚合维度 示例 合并规则
主机维度 同一台服务器的 CPU、内存、磁盘告警 合并为「主机异常」
服务维度 同一个微服务的多个实例告警 合并为「服务故障」
依赖维度 数据库告警 + 依赖该库的应用告警 只保留根因(数据库告警)

3.1.3 相似度聚合

这个稍微高级一点。有些告警内容不完全一样,但语义相似。比如「连接池耗尽」和「获取连接超时」,其实是一回事。

我建议用 Levenshtein 距离或者简单的关键词匹配来做。别搞太复杂,生产环境里 80% 的相似告警都能用关键词覆盖。

核心原则:聚合不是「消灭告警」,而是「让告警更有价值」。聚合后的告警,应该让值班人员一眼就能看出「哪里出了问题」。

3.2 告警抑制规则:别让「果」掩盖了「因」

告警抑制,说白了就是因果关系的处理。

你想想看,如果数据库挂了,那所有依赖它的应用都会报错。这时候如果每条应用告警都发出去,那数据库的根因告警反而被淹没了。

我曾经踩过一个坑:某次线上故障,数据库主从延迟导致前端大量报 502。结果告警系统发了 300 条「502 错误率过高」,而真正的「主从延迟」告警只发了一条,还被淹没了。值班同学花了 20 分钟才找到根因。

从那以后,我定了一条铁律:所有告警必须带上「依赖关系」标签。

3.2.1 层级抑制

最常见的抑制方式。比如:

  • 如果「交换机宕机」告警触发,则抑制该交换机下所有服务器的「网络不可达」告警
  • 如果「数据库不可用」告警触发,则抑制所有「应用连接超时」告警
# 抑制规则示例(Prometheus Alertmanager 风格)
groups:
  - name: network
    rules:
      - alert: SwitchDown
        expr: switch_status == 0
        labels:
          severity: critical
      - alert: HostUnreachable
        expr: probe_success == 0
        labels:
          severity: warning
        # 如果 SwitchDown 已触发,则抑制 HostUnreachable
        inhibit_rules:
          - source_match:
              alertname: SwitchDown
            target_match:
              alertname: HostUnreachable

3.2.2 频率抑制

有些告警会反复触发、恢复、再触发。比如网络抖动,可能每 5 分钟抖一次。如果每次都发告警,那值班人员就不用干别的了。

我的做法是:同一个告警在 1 小时内只发一次。如果再次触发,只更新「最近一次触发时间」,不重复发送。

小技巧:频率抑制的窗口建议跟告警的恢复时间挂钩。如果告警恢复得很快(比如 1 分钟内),那抑制窗口可以设长一点。如果告警持续存在,那抑制窗口可以短一些。

3.3 告警静默机制:给告警系统「放个假」

告警静默,就是主动告诉系统:「这段时间别吵我,我知道有问题。」

我见过很多团队把静默当成「掩耳盗铃」——出了问题不想修,就静默掉。这其实是大忌。

3.3.1 计划内静默

比如凌晨 2 点的版本发布、周末的机房维护。这些操作肯定会触发告警,但都是预期内的。

我的习惯是:提前创建静默规则,带上明确的「原因」和「负责人」。

# 静默规则示例
{
  "name": "版本发布静默",
  "start": "2024-01-15 02:00:00",
  "end": "2024-01-15 04:00:00",
  "matchers": [
    {"key": "service", "value": "order-service"},
    {"key": "env", "value": "production"}
  ],
  "reason": "v3.2.1 版本发布,预计影响订单服务",
  "owner": "张三"
}

3.3.2 临时静默

有时候线上出了故障,但短时间内修不好。比如磁盘满了,但扩容需要 2 小时。这时候如果每 5 分钟报一次「磁盘告警」,那值班人员会疯掉。

我建议:临时静默一定要设置「自动过期」。最长不超过 4 小时。到期后如果问题还在,告警会重新触发,提醒你「嘿,这问题还没修呢」。

警告:千万不要用「永久静默」!我曾经接手过一个系统,里面有 200 多条永久静默规则。结果某天真的出了故障,但因为被静默了,没人知道。等用户投诉了才发现——系统已经静默了三个月。

3.3.3 静默的审计与复盘

我个人要求团队:每次静默都要记录到工单系统。每周复盘一次,看看哪些静默是合理的,哪些是「懒得修」的。

嗯,这里有个小经验:如果某个告警被反复静默,那说明这个告警的阈值或者规则本身有问题。与其反复静默,不如直接调整告警策略。

3.4 三者如何配合?

聚合、抑制、静默,这三者不是孤立的。我一般这样搭配:

  1. 先聚合:把相似的告警合并,减少数量
  2. 再抑制:把因果关系的告警过滤掉,保留根因
  3. 最后静默:对已知的、计划内的问题,主动屏蔽

举个例子:某次数据库主从切换,会触发 100 条告警。经过聚合后变成 10 条,再经过抑制后剩下 3 条(主从切换、连接池变化、延迟告警)。如果这是计划内的切换,那直接静默掉,一条告警都不会发。

你看,经过这三层处理,值班人员收到的告警从 100 条变成了 0 条。但注意——不是「没有告警」,而是「不需要关注的告警被过滤了」。真正的根因告警,一条都不会少。

总结一句话:告警去重与降噪,不是为了「让告警变少」,而是为了「让告警变准」。一个优秀的告警系统,应该让值班人员「看一条告警就知道该干啥」。

好了,这一章就聊到这儿。下一章咱们讲讲告警的升级策略——当告警没人处理时,系统该怎么「催」你?