从零搭建工业级监控告警体系:告警规则设计、分级策略与告警风暴治理
在凯泵智联(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 中间件监控
其中 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: critical3.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 三级告警体系
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 短暂不可用场景:
六、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 获取更多实战分享。