从零搭建工业级监控告警体系:告警规则设计、分级策略与告警风暴治理

在凯泵智联(KiCloud)云平台中,我搭建了 Prometheus + Alertmanager + Grafana 的全链路监控告警体系,覆盖了 120+ 企业客户的设备监控服务。本文记录告警规则怎么设计才不会"狼来了"、分级策略怎么让值班人员不崩溃、以及告警风暴的工程化治理方案。

一、为什么要自建监控体系

1.1 背景

凯泵智联是一个 SaaS 化的旋转类设备远程监测平台,基于 Spring Cloud Alibaba 微服务架构,部署在客户的私有云和我们的公有云混合环境中。系统包含十多个微服务、MySQL、Redis、Kafka、TrendDB 等中间件,以及通过 MQTT 协议连接的几千台现场设备。

上线初期没有系统性的监控方案,出了问题全靠用户报障。有一次某个客户的设备数据停止更新了 4 个小时,我们毫不知情,直到客户打电话质问才发现是 Kafka 消费者线程挂了。这件事让团队意识到"不可观测的系统就是定时炸弹"。

1.2 技术选型

选择 Prometheus + Alertmanager + Grafana(PAG)而非 ELK、Zabbix 或商业 APM(如 DataDog)的原因:

Prometheus 是云原生事实标准,和 Spring Boot Actuator 天然集成(通过 Micrometer),Java 微服务零代码改动即可暴露指标。拉模式(pull)采集不需要在服务端安装 agent,运维简单。

Alertmanager 原生对接 Prometheus,支持告警路由、分组、抑制、静默等高级功能,这些是避免告警风暴的关键能力。

Grafana 的可视化能力远超 Prometheus 自带的 UI,而且支持多数据源(Prometheus、MySQL、Elasticsearch 都能接入同一个 Dashboard)。

全套开源免费,部署在 3 台 2C4G 的服务器上,月成本约 ¥600。

二、监控指标设计:采集什么

2.1 监控分层模型

我按照"四层模型"组织监控指标,从上到下分别是:

┌─────────────────────────────┐
│   业务层:核心业务指标        │  设备在线率、数据采集成功率、工单处理时效
├─────────────────────────────┤
│   应用层:微服务运行指标      │  接口 RT、错误率、吞吐量、JVM 指标
├─────────────────────────────┤
│   中间件层:基础组件指标      │  MySQL QPS、Redis 命中率、Kafka 消费延迟
├─────────────────────────────┤
│   基础设施层:服务器资源指标   │  CPU、内存、磁盘、网络
└─────────────────────────────┘

2.2 自定义业务指标埋点

Spring Boot Actuator + Micrometer 自动暴露的指标(JVM、HTTP 请求等)只覆盖了应用层。业务层的核心指标需要手动埋点:

@Component
public class DeviceMetrics {
​
    private final MeterRegistry registry;
    private final AtomicInteger onlineDeviceCount;
    private final Counter dataCollectSuccessCounter;
    private final Counter dataCollectFailCounter;
    private final Timer dataProcessTimer;
​
    public DeviceMetrics(MeterRegistry registry) {
        this.registry = registry;
​
        // 设备在线数(Gauge 类型,反映当前状态)
        this.onlineDeviceCount = registry.gauge("device.online.count",
            new AtomicInteger(0));
​
        // 数据采集成功/失败计数器(Counter 类型,只增不减)
        this.dataCollectSuccessCounter = registry.counter("device.collect",
            "result", "success");
        this.dataCollectFailCounter = registry.counter("device.collect",
            "result", "fail");
​
        // 数据处理耗时(Timer 类型,记录分布)
        this.dataProcessTimer = registry.timer("device.process.duration");
    }
​
    public void recordCollectSuccess() {
        dataCollectSuccessCounter.increment();
    }
​
    public void recordCollectFail(String reason) {
        dataCollectFailCounter.increment();
        registry.counter("device.collect.fail.reason",
            "reason", reason).increment();
    }
​
    public void recordProcessDuration(long millis) {
        dataProcessTimer.record(millis, TimeUnit.MILLISECONDS);
    }
​
    public void updateOnlineCount(int count) {
        onlineDeviceCount.set(count);
    }
}

Gauge vs Counter vs Timer 的选择原则: 当前状态值用 Gauge(如在线设备数、队列长度),累计计数用 Counter(如请求总数、错误总数),耗时分布用 Timer(如接口 RT)。选错类型会导致 Prometheus 查询语句写不对或结果不准确。

2.3 中间件监控

中间件

Exporter

关键指标

MySQL

mysqld_exporter

QPS、慢查询数、连接数、主从延迟

Redis

redis_exporter

命中率、内存使用率、连接数、Key 数量

Kafka

kafka_exporter

消费者 Lag、分区 Leader 分布、ISR 变化

Node

node_exporter

CPU、内存、磁盘 IO、网络带宽

其中 Kafka 消费者 Lag 是最关键的中间件指标。凯泵智联的设备数据通过 Kafka 传输,如果消费者 Lag 持续增长,说明数据处理能力跟不上生产速度,最终会导致设备数据延迟。那次 4 小时没发现的故障,就是消费者 Lag 从 0 飙到了几十万没人看到。

三、告警规则设计

3.1 告警规则的"三要素"

一条好的告警规则必须回答三个问题:什么指标(What)出了问题、严重到什么程度(How bad)才需要告警、持续多长时间(How long)才确认不是毛刺。

以接口错误率告警为例:

# Prometheus 告警规则
groups:
  - name: application_alerts
    rules:
      - alert: HighErrorRate
        expr: |
          (
            sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (application, uri)
            /
            sum(rate(http_server_requests_seconds_count[5m])) by (application, uri)
          ) > 0.05
        for: 3m
        labels:
          severity: warning
          team: backend
        annotations:
          summary: "{{ $labels.application }} 接口 {{ $labels.uri }} 错误率过高"
          description: "过去5分钟错误率 {{ $value | humanizePercentage }},超过5%阈值,已持续3分钟"

What: 某个服务某个接口的 5xx 错误占比。

How bad: 超过 5%。这个阈值不是随便定的——分析了三个月的历史数据,正常情况下错误率在 0.1%-0.5% 之间波动,超过 5% 意味着已经偏离正常范围 10 倍以上。

How long: 持续 3 分钟。短暂的毛刺(如一次超时重试)不告警,持续 3 分钟说明不是偶发。

3.2 核心告警规则清单

按四层模型设计的核心告警规则:

业务层告警(直接影响用户体验):

# 设备在线率下降
- alert: DeviceOnlineRateDrop
  expr: device_online_count / device_total_count < 0.85
  for: 5m
  labels:
    severity: critical
​
# Kafka 消费延迟(设备数据处理积压)
- alert: KafkaConsumerLagHigh
  expr: kafka_consumergroup_lag_sum{group="device-data-consumer"} > 10000
  for: 5m
  labels:
    severity: critical

应用层告警:

# 接口 P99 响应时间过高
- alert: HighP99Latency
  expr: histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m])) > 2
  for: 3m
  labels:
    severity: warning
​
# JVM 堆内存使用率过高
- alert: JvmHeapHigh
  expr: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.85
  for: 5m
  labels:
    severity: warning
​
# Full GC 频繁
- alert: FrequentFullGC
  expr: increase(jvm_gc_pause_seconds_count{action="end of major GC"}[10m]) > 3
  for: 0m
  labels:
    severity: warning

中间件层告警:

# MySQL 慢查询激增
- alert: MysqlSlowQueries
  expr: increase(mysql_global_status_slow_queries[5m]) > 20
  for: 3m
  labels:
    severity: warning
​
# Redis 内存使用率过高
- alert: RedisMemoryHigh
  expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.8
  for: 5m
  labels:
    severity: warning
​
# MySQL 主从延迟
- alert: MysqlReplicationLag
  expr: mysql_slave_status_seconds_behind_master > 30
  for: 2m
  labels:
    severity: critical

基础设施层告警:

# CPU 使用率持续过高
- alert: HighCPU
  expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
  for: 10m
  labels:
    severity: warning
​
# 磁盘空间不足
- alert: DiskSpaceLow
  expr: (node_filesystem_avail_bytes / node_filesystem_size_bytes) < 0.15
  for: 5m
  labels:
    severity: critical

3.3 告警阈值的确定方法

告警阈值不是拍脑袋定的。我用了两种方法:

历史数据分析法。 用 Prometheus 查询过去 30 天的指标数据,计算均值和标准差。告警阈值 = 均值 + 3 倍标准差。例如接口 RT 的均值是 200ms、标准差是 50ms,那么告警阈值设为 200 + 3×50 = 350ms(取整到 500ms 留余量)。

业务 SLA 倒推法。 和产品、客户约定的 SLA 是"设备数据延迟不超过 30 秒"。从这个 SLA 倒推:Kafka 消费者 Lag 超过 10000 条(按每秒处理 500 条算,积压 10000 条意味着延迟 20 秒,离 30 秒的红线只剩 10 秒缓冲),就应该告警。

四、告警分级与路由策略

4.1 三级告警体系

级别

含义

响应要求

通知方式

P0 Critical

系统不可用或核心功能严重受损

5 分钟内响应

电话 + 短信 + 钉钉群

P1 Warning

系统有性能劣化或潜在风险

30 分钟内响应

钉钉群 + 邮件

P2 Info

需要关注但不紧急的状态变化

下一个工作日处理

邮件

4.2 Alertmanager 路由配置

# alertmanager.yml
route:
  receiver: 'default-webhook'
  group_by: ['alertname', 'application']
  group_wait: 30s        # 同组告警等待30秒聚合后再发送
  group_interval: 5m     # 同组告警的发送间隔
  repeat_interval: 4h    # 已发送告警的重复提醒间隔
​
  routes:
    # P0: 电话 + 短信 + 钉钉
    - match:
        severity: critical
      receiver: 'critical-all-channels'
      group_wait: 10s      # 紧急告警缩短聚合等待
      repeat_interval: 30m # 未处理的话每30分钟重复提醒
​
    # P1: 钉钉群 + 邮件
    - match:
        severity: warning
      receiver: 'warning-dingtalk-email'
      repeat_interval: 2h
​
    # P2: 仅邮件
    - match:
        severity: info
      receiver: 'info-email'
      repeat_interval: 12h
​
receivers:
  - name: 'critical-all-channels'
    webhook_configs:
      - url: 'http://alert-gateway:8080/api/alert/critical'
        # 内部网关,负责同时触发电话、短信、钉钉
​
  - name: 'warning-dingtalk-email'
    webhook_configs:
      - url: 'https://oapi.dingtalk.com/robot/send?access_token=xxx'
    email_configs:
      - to: 'backend-oncall@company.com'
​
  - name: 'info-email'
    email_configs:
      - to: 'backend-team@company.com'

五、告警风暴治理

5.1 什么是告警风暴

当系统发生全局性故障(如数据库宕机、网络分区)时,几十条告警在几分钟内同时触发——数据库连接失败、接口错误率飙升、Kafka 消费延迟激增、设备在线率下降……每条告警单独看都是对的,但同时收到几十条,值班人员根本分不清"根因是什么",只感到恐慌和信息过载。

我们经历过一次:MySQL 主节点短暂不可用(约 2 分钟),触发了 37 条告警。值班同事的手机在 5 分钟内震了 37 次,等他看完所有告警短信,MySQL 已经自动恢复了。

5.2 Alertmanager 的三板斧

第一板斧:分组(Grouping)。

group_by: ['alertname', 'application']
group_wait: 30s

同一个告警名 + 同一个服务产生的多条告警,聚合为一条通知。比如 5 个实例同时报 HighErrorRate,不会收到 5 条消息,而是 1 条消息里列出"device-service 的 5 个实例错误率过高"。

第二板斧:抑制(Inhibition)。

inhibit_rules:
  # 如果已有数据库不可用的 critical 告警,
  # 则抑制由此引发的应用层 warning 告警
  - source_match:
      alertname: MysqlDown
      severity: critical
    target_match:
      severity: warning
    equal: ['cluster']   # 同一集群内的告警才抑制

  # 如果节点宕机了,抑制该节点上所有应用的告警
  - source_match:
      alertname: NodeDown
      severity: critical
    target_match:
      severity: warning
    equal: ['instance']

抑制的逻辑是:如果根因告警(数据库宕机)已经触发了,那么它导致的衍生告警(接口错误率高、消费延迟大)就不再单独通知。值班人员只需要关注根因。

第三板斧:静默(Silence)。

在计划内的维护窗口(如数据库升级),提前在 Alertmanager 中创建静默规则,临时屏蔽相关告警,避免维护操作触发无意义的告警风暴。

# 创建静默规则:在 2025-01-20 02:00-04:00 屏蔽 MySQL 相关的所有告警
amtool silence add \
  alertname=~"Mysql.*" \
  --start="2025-01-20T02:00:00+08:00" \
  --end="2025-01-20T04:00:00+08:00" \
  --comment="MySQL主从切换维护窗口"

5.3 实际效果

那次 37 条告警风暴之后,配置了分组和抑制规则。模拟同样的 MySQL 短暂不可用场景:

指标

优化前

优化后

收到的通知数量

37 条

3 条(1条根因 + 2条未被抑制的独立告警)

值班人员定位根因时间

15 分钟

1 分钟

值班人员情绪

恐慌

从容

六、Grafana Dashboard 设计

6.1 Dashboard 分层

对应监控的四层模型,设计了四套 Dashboard:

Executive Overview(全局概览)。 一屏展示系统健康度:设备在线率、核心接口成功率、基础设施资源使用率。给管理层和客户看的,没有技术细节。

Application Dashboard(应用监控)。 每个微服务一个 Dashboard,包含接口 QPS/RT/错误率、JVM 堆内存/GC、线程池状态等。日常开发排查问题用。

Middleware Dashboard(中间件监控)。 MySQL、Redis、Kafka 各一个 Dashboard。DBA 和运维用。

Business Dashboard(业务监控)。 设备数据采集成功率、各客户的设备在线率趋势、工单处理时效等。运营和客户成功团队用。

6.2 实用技巧

关键指标放在左上角。 人的视线自然从左上角开始扫描,最重要的指标(如设备在线率、核心接口成功率)放在 Dashboard 的左上角位置。

颜色阈值配置。 Grafana 的 Stat 面板支持颜色阈值——正常绿色、临界黄色、异常红色。值班人员一扫就能看出哪里有问题,不需要逐个看数字。

变量选择器。 在 Dashboard 顶部配置变量(环境、服务名、时间范围),一套 Dashboard 模板适用于所有服务和环境,避免为每个服务单独建 Dashboard。

七、经验总结

监控不是搭完就结束的项目,而是需要持续演进的系统。 我们的告警规则在过去一年调整了 20+ 次,每次误报都是一次优化机会——要么阈值不合理,要么 for 时间窗口太短,要么缺少抑制规则。

误报比漏报更有害。 误报多了值班人员就会把告警当噪音忽略("狼来了"效应),导致真正的故障也被忽略。宁可阈值松一点、for 长一点,也不要让误报淹没真正的告警。

告警通知不是越多越好。 分组、抑制、静默这三个能力是 Alertmanager 的精髓,没有它们的告警系统就是一个"告警炸弹"。

监控的终极目标是"无需看 Dashboard 就知道系统健康"。 如果值班人员需要时刻盯着 Dashboard 才能发现问题,说明告警规则还不够完善。理想状态是:没有告警 = 系统健康,有告警 = 精准告知问题和定位方向。


如果这篇文章对你有帮助,欢迎访问我的博客 robinzhu.top 获取更多实战分享。