📊 设计一个监控告警系统:医生的听诊器!

39 阅读8分钟

📖 开场:体检中心

想象医院的体检中心 🏥:

没有监控(后知后觉)

病人:感觉不舒服 😷
    ↓
去医院检查
    ↓
医生:癌症晚期 💀
    ↓
错过最佳治疗时机 ❌

结果:
- 发现太晚 ❌
- 治疗困难 ❌

有监控(防患未然)

体检中心:定期体检 🩺
    ↓
发现指标异常:血压180(警告)⚠️
    ↓
立即通知:请到医院就诊 📞
    ↓
及时治疗,避免恶化 ✅

结果:
- 早发现 ✅
- 早治疗 ✅
- 成本低 ✅

这就是监控系统:系统的体检中心!


🤔 为什么需要监控告警?

问题1:系统故障不知道 💀

没有监控:
服务器宕机 💀
    ↓
用户无法访问 ❌
    ↓
用户投诉 📞
    ↓
你才知道出问题了 😱

有监控:
服务器宕机 💀
    ↓
监控系统:立即告警 🚨
    ↓
运维人员:5分钟内修复 ✅

问题2:性能下降不知道 🐌

没有监控:
接口响应时间:从100ms → 5秒
    ↓
用户体验变差 ❌
    ↓
用户流失 💔

有监控:
响应时间 > 1秒 → 告警 ⚠️
    ↓
立即优化 ✅

🎯 核心功能

功能1:指标采集 📥

采集指标:
- 系统指标:CPU、内存、磁盘、网络
- 应用指标:QPS、响应时间、错误率
- 业务指标:订单量、GMV、用户活跃度

功能2:数据存储 💾

时序数据库(Time Series Database):
- InfluxDB
- Prometheus
- OpenTSDB

特点:
- 按时间排序 ✅
- 高效压缩 ✅
- 快速查询 ✅

功能3:告警规则 🚨

告警规则:
1. CPU使用率 > 80%(持续5分钟)
2. 内存使用率 > 90%
3. 磁盘使用率 > 85%
4. 接口响应时间 > 1秒
5. 错误率 > 5%

功能4:告警通知 📢

通知渠道:
- 钉钉机器人 🤖
- 企业微信 💬
- 短信 📱
- 邮件 📧
- 电话 ☎️

🎯 系统架构

        监控告警系统架构

┌────────────────────────────────────┐
│        监控对象                     │
│  - 服务器                          │
│  - 应用服务                        │
│  - 数据库                          │
│  - 中间件                          │
└──────────────┬─────────────────────┘
               │ 上报指标
               ↓
┌────────────────────────────────────┐
│      指标采集(Agent)              │
│  - node_exporter(系统指标)       │
│  - 应用埋点(应用指标)            │
└──────────────┬─────────────────────┘
               │
               ↓
┌────────────────────────────────────┐
│      时序数据库(Prometheus)       │
│  - 存储指标数据                    │
│  - 聚合查询                        │
└──────────────┬─────────────────────┘
               │
       ┌───────┼───────┐
       ↓       ↓       ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 告警规则 │ │ Grafana  │ │AlertMgr  │
│ 阈值判断 │ │ 可视化   │ │告警发送  │
└──────────┘ └──────────┘ └──────────┘
                              ↓
                        ┌──────────┐
                        │ 钉钉通知 │
                        └──────────┘

🎯 核心设计

设计1:Prometheus监控 ⭐⭐⭐

安装Prometheus

# 下载Prometheus
wget https://github.com/prometheus/prometheus/releases/download/v2.40.0/prometheus-2.40.0.linux-amd64.tar.gz

# 解压
tar -xzf prometheus-2.40.0.linux-amd64.tar.gz
cd prometheus-2.40.0.linux-amd64

# 启动
./prometheus --config.file=prometheus.yml

配置prometheus.yml

# ⭐ Prometheus配置
global:
  scrape_interval: 15s  # 抓取间隔
  evaluation_interval: 15s  # 告警规则评估间隔

# ⭐ 告警规则文件
rule_files:
  - "rules/*.yml"

# ⭐ 告警管理器
alerting:
  alertmanagers:
    - static_configs:
        - targets: ['localhost:9093']

# ⭐ 监控目标
scrape_configs:
  # 监控Prometheus自己
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']
  
  # ⭐ 监控服务器(node_exporter)
  - job_name: 'node'
    static_configs:
      - targets:
          - '192.168.1.1:9100'
          - '192.168.1.2:9100'
          - '192.168.1.3:9100'
  
  # ⭐ 监控应用服务
  - job_name: 'order-service'
    static_configs:
      - targets: ['192.168.1.10:8080']

设计2:应用埋点(Micrometer)

引入依赖

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

配置application.yml

management:
  endpoints:
    web:
      exposure:
        include: '*'  # 暴露所有端点
  metrics:
    export:
      prometheus:
        enabled: true  # 启用Prometheus导出

自定义指标

@Service
public class OrderService {
    
    // ⭐ 定义计数器(订单数)
    private final Counter orderCounter;
    
    // ⭐ 定义计时器(接口响应时间)
    private final Timer orderTimer;
    
    public OrderService(MeterRegistry meterRegistry) {
        // 注册计数器
        this.orderCounter = Counter.builder("order_total")
            .tag("service", "order-service")
            .description("订单总数")
            .register(meterRegistry);
        
        // 注册计时器
        this.orderTimer = Timer.builder("order_create_time")
            .tag("service", "order-service")
            .description("创建订单耗时")
            .register(meterRegistry);
    }
    
    /**
     * ⭐ 创建订单(埋点)
     */
    public Order createOrder(OrderRequest request) {
        return orderTimer.record(() -> {
            // 创建订单逻辑
            Order order = new Order();
            // ...
            orderMapper.insert(order);
            
            // ⭐ 计数器+1
            orderCounter.increment();
            
            return order;
        });
    }
}

访问指标

http://localhost:8080/actuator/prometheus

# 输出:
order_total{service="order-service"} 12345
order_create_time_seconds_sum{service="order-service"} 123.456
order_create_time_seconds_count{service="order-service"} 12345

设计3:告警规则

创建rules/alert.yml

groups:
  - name: server_alerts
    interval: 30s  # 评估间隔
    rules:
      # ⭐ CPU告警
      - alert: HighCPU
        expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 5m  # 持续5分钟
        labels:
          severity: warning
        annotations:
          summary: "服务器CPU使用率过高"
          description: "{{ $labels.instance }} CPU使用率 {{ $value | humanize }}%"
      
      # ⭐ 内存告警
      - alert: HighMemory
        expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100 > 90
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "服务器内存使用率过高"
          description: "{{ $labels.instance }} 内存使用率 {{ $value | humanize }}%"
      
      # ⭐ 磁盘告警
      - alert: HighDisk
        expr: (node_filesystem_size_bytes - node_filesystem_free_bytes) / node_filesystem_size_bytes * 100 > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "服务器磁盘使用率过高"
          description: "{{ $labels.instance }} 磁盘使用率 {{ $value | humanize }}%"

  - name: application_alerts
    interval: 30s
    rules:
      # ⭐ 接口响应时间告警
      - alert: SlowAPI
        expr: histogram_quantile(0.95, rate(order_create_time_seconds_bucket[5m])) > 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "接口响应时间过长"
          description: "订单创建接口P95响应时间 {{ $value | humanize }}秒"
      
      # ⭐ 错误率告警
      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) * 100 > 5
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "接口错误率过高"
          description: "错误率 {{ $value | humanize }}%"

设计4:AlertManager告警管理

安装AlertManager

# 下载
wget https://github.com/prometheus/alertmanager/releases/download/v0.25.0/alertmanager-0.25.0.linux-amd64.tar.gz

# 解压
tar -xzf alertmanager-0.25.0.linux-amd64.tar.gz
cd alertmanager-0.25.0.linux-amd64

# 启动
./alertmanager --config.file=alertmanager.yml

配置alertmanager.yml

global:
  resolve_timeout: 5m  # 告警恢复超时时间

# ⭐ 路由规则
route:
  group_by: ['alertname']  # 按告警名称分组
  group_wait: 10s  # 等待10秒收集同组告警
  group_interval: 10s  # 同组告警间隔10秒
  repeat_interval: 1h  # 重复告警间隔1小时
  receiver: 'dingding'  # 默认接收者

# ⭐ 接收者配置
receivers:
  - name: 'dingding'
    webhook_configs:
      - url: 'http://localhost:8060/dingtalk/webhook'
        send_resolved: true  # 发送恢复通知

设计5:钉钉告警通知

代码实现

@RestController
@RequestMapping("/dingtalk")
public class DingTalkWebhook {
    
    private String webhookUrl = "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN";
    
    @Autowired
    private RestTemplate restTemplate;
    
    /**
     * ⭐ 接收AlertManager Webhook
     */
    @PostMapping("/webhook")
    public void receiveAlert(@RequestBody AlertManagerWebhook webhook) {
        List<Alert> alerts = webhook.getAlerts();
        
        for (Alert alert : alerts) {
            String alertName = alert.getLabels().get("alertname");
            String severity = alert.getLabels().get("severity");
            String summary = alert.getAnnotations().get("summary");
            String description = alert.getAnnotations().get("description");
            String status = alert.getStatus();  // firing/resolved
            
            // ⭐ 构造钉钉消息
            String message = String.format(
                "## %s告警%s\n\n" +
                "**级别**: %s\n\n" +
                "**摘要**: %s\n\n" +
                "**详情**: %s\n\n" +
                "**时间**: %s",
                status.equals("firing") ? "🚨" : "✅",
                status.equals("firing") ? "" : "恢复",
                severity,
                summary,
                description,
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
            );
            
            // 发送到钉钉
            sendToDingTalk(message);
        }
    }
    
    /**
     * ⭐ 发送钉钉消息
     */
    private void sendToDingTalk(String content) {
        Map<String, Object> request = new HashMap<>();
        request.put("msgtype", "markdown");
        
        Map<String, String> markdown = new HashMap<>();
        markdown.put("title", "监控告警");
        markdown.put("text", content);
        request.put("markdown", markdown);
        
        restTemplate.postForObject(webhookUrl, request, String.class);
    }
}

🎓 面试题速答

Q1: 监控系统有哪些组件?

A: 三大组件

  1. Prometheus(时序数据库):

    • 存储指标数据
    • 执行告警规则
  2. AlertManager(告警管理):

    • 告警聚合
    • 告警降噪
    • 告警路由
  3. Grafana(可视化):

    • 指标展示
    • Dashboard

Q2: 如何采集应用指标?

A: Micrometer埋点

// 引入依赖
micrometer-registry-prometheus
spring-boot-starter-actuator

// 自定义指标
Counter counter = Counter.builder("order_total")
    .register(meterRegistry);

counter.increment();  // 计数器+1

// 访问指标
http://localhost:8080/actuator/prometheus

Q3: 告警规则如何配置?

A: PromQL表达式

# CPU告警
- alert: HighCPU
  expr: 100 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100 > 80
  for: 5m  # 持续5分钟

含义:CPU使用率 > 80%,持续5分钟触发告警


Q4: 如何避免告警风暴?

A: 四种降噪策略

  1. 分组聚合

    • 同类告警合并发送
  2. 抑制规则

    • 高优先级告警触发时,抑制低优先级
  3. 静默规则

    • 维护期间暂停告警
  4. 重复间隔

    • 1小时内不重复发送

Q5: 监控指标有哪些?

A: 四大黄金指标

  1. 延迟(Latency)

    • 接口响应时间
  2. 流量(Traffic)

    • QPS、TPS
  3. 错误(Errors)

    • 错误率、错误数
  4. 饱和度(Saturation)

    • CPU、内存、磁盘使用率

Q6: 时序数据库的特点?

A: 三大特点

  1. 按时间排序

    • 时间戳是主键
  2. 高效压缩

    • 1000个数据点 → 12字节
  3. 快速聚合

    • avg、sum、rate等

例子

Prometheus查询:
rate(http_requests_total[5m])  # 最近5分钟的QPS

🎬 总结

       监控告警系统核心

┌────────────────────────────────────┐
│ 1. Prometheus(时序数据库)⭐       │
│    - 指标采集                      │
│    - 数据存储                      │
│    - 告警规则                      │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 2. 应用埋点(Micrometer)          │
│    - Counter(计数器)             │
│    - Timer(计时器)               │
│    - Gauge(仪表盘)               │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 3. 告警规则(PromQL)              │
│    - CPU > 80%                     │
│    - 内存 > 90%                    │
│    - 响应时间 > 1秒                │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 4. AlertManager(告警管理)        │
│    - 告警聚合                      │
│    - 告警降噪                      │
│    - 告警路由                      │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 5. 钉钉通知                        │
│    - Webhook接收告警               │
│    - Markdown格式                  │
└────────────────────────────────────┘

🎉 恭喜你!

你已经完全掌握了监控告警系统的设计!🎊

核心要点

  1. Prometheus:时序数据库,指标采集和存储
  2. Micrometer:应用埋点,自定义指标
  3. 告警规则:PromQL表达式,阈值判断
  4. AlertManager:告警聚合、降噪、路由
  5. 钉钉通知:Webhook接收,Markdown格式

下次面试,这样回答

"监控系统采用Prometheus + AlertManager + Grafana架构。Prometheus作为时序数据库,负责指标采集、存储和告警规则评估。应用服务集成Micrometer和Actuator,暴露/actuator/prometheus端点,Prometheus定时拉取指标数据。

应用埋点使用Micrometer自定义指标。创建Counter计数器统计订单数,Timer计时器统计接口响应时间。这些指标按Prometheus格式导出,Prometheus每15秒拉取一次。系统指标使用node_exporter采集,包括CPU、内存、磁盘、网络等。

告警规则使用PromQL表达式配置。例如CPU告警规则:100减去idle模式CPU比例的5分钟平均值,大于80%且持续5分钟触发告警。接口响应时间告警:P95分位数超过1秒触发。错误率告警:5xx错误占比超过5%触发。

AlertManager负责告警管理。接收Prometheus告警后,按alertname分组,等待10秒收集同组告警再发送,避免告警风暴。配置重复间隔1小时,防止频繁发送。通过Webhook发送到钉钉机器人,使用Markdown格式展示告警信息,包括级别、摘要、详情和时间。

可视化使用Grafana展示。导入Prometheus数据源,创建Dashboard展示CPU、内存、QPS、响应时间等指标。配置告警Panel,直观看到当前告警数量。监控四大黄金指标:延迟、流量、错误和饱和度,全面掌握系统健康状况。"

面试官:👍 "很好!你对监控系统的设计理解很深刻!"


本文完 🎬

上一篇: 219-设计一个推荐系统架构.md
下一篇: 221-设计一个电商库存系统.md

作者注:写完这篇,我觉得自己可以当运维工程师了!📊
如果这篇文章对你有帮助,请给我一个Star⭐!