Skip to content

微服务监控体系:Prometheus + Grafana + 自定义指标,SLO/SLI/SLA 落地

提出问题

微服务架构下,服务数量从 10 个增长到 100 个,每个服务都有独立的 CPU、内存、线程池、GC 行为、请求量、延迟、错误率。运维人员面对几十个 Grafana 面板,每天收到 200+ 条告警,但真正需要人工介入的故障反而被淹没在告警噪音里。更常见的问题是:某个服务出错率升到 5%,团队觉得"还行"没处理,等发现问题时已经影响了 10 分钟的用户体验

问题出在哪里?不是工具不够,是没有把监控和业务目标绑定。没有 SLO(服务等级目标),你就没法回答"5% 的错误率到底算不算严重";没有 Error Budget,你就没法在可用性和发布速度之间做理性决策。本文从四层监控体系搭建讲起,一直到 SLO/SLI 的落地实践,帮你把监控从"搭几个大盘"升级为"驱动架构决策的量化体系"。

分析问题

四层监控体系:从基础设施到业务指标

微服务监控不能只盯着 CPU 和请求量,一个完整的监控体系应该覆盖四个层次:

第一层:基础设施层。CPU、内存、磁盘、网络、IO 等操作系统级指标,通过 Node Exporter 暴露给 Prometheus。这一层是"服务还活着吗"的底线判断,但不能作为核心告警源——CPU 飙到 90% 不代表服务不可用,很多服务在 90% CPU 下仍然正常运行。

第二层:中间件层。MySQL、Redis、Kafka、Nginx 等中间件的运行状态。Redis Exporter 采集 hit/miss 率、内存碎片率;Kafka Exporter 采集消费组 lag、分区 Leader 分布;MySQL Exporter 采集连接数、慢查询数、InnoDB 状态。这一层往往是被忽视的——很多团队只监控应用层,等 MySQL 连接池满了才发现问题。

第三层:应用层——RED 指标。Google SRE 提出的 RED 方法论:Rate(请求量)、Errors(错误率)、Duration(延迟)。每个服务至少暴露三个指标:

java
// Micrometer 埋点示例
@RestController
public class OrderController {
    private final MeterRegistry meterRegistry;
    private final Counter orderCreateCounter;
    private final Timer orderCreateTimer;

    public OrderController(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        this.orderCreateCounter = Counter.builder("order_create_total")
            .tag("service", "order-service")
            .register(meterRegistry);
        this.orderCreateTimer = Timer.builder("order_create_duration_seconds")
            .tag("service", "order-service")
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(meterRegistry);
    }

    @PostMapping("/orders")
    public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest req) {
        return orderCreateTimer.record(() -> {
            Order order = orderService.create(req);
            orderCreateCounter.increment();
            return ResponseEntity.ok(order);
        });
    }
}

Spring Boot 应用只需引入 micrometer-registry-prometheus,在 application.yml 中配置暴露 /actuator/prometheus 端点,Prometheus 自动抓取。注意:不要暴露 envuser 等敏感信息到 Prometheus 标签中。

第四层:业务层。订单量、支付成功率、转化率、用户活跃度等业务特有指标。这一层由业务团队自己定义,通过 Micrometer 的 Gauge/Counter 注册。业务指标的价值在于:当应用层指标正常但业务指标异常时,说明业务逻辑有 bug

业务层指标落地:一个真实案例

2024 年我在一家电商平台做过一次排查:Grafana 看应用层 RED 指标全部正常——请求量 2000 QPS、P99 延迟 150ms、错误率 0.01%,但业务团队反馈"今天下单成功量比昨天少了 30%"。为什么?因为下单接口返回了 200,但库存扣减服务返回了「库存不足」业务错误——HTTP 状态码是 200,业务 code 是 4001。应用层指标只盯着 HTTP 状态码,根本抓不到。

解决方案:业务层单独埋点,区分「技术错误」(HTTP 5xx)和「业务错误」(业务异常码):

java
// 业务异常埋点
@Aspect
@Component
public class BusinessMetricAspect {
    private final Counter bizErrorCounter;

    public BusinessMetricAspect(MeterRegistry registry) {
        this.bizErrorCounter = Counter.builder("business_error_total")
            .tag("service", "order-service")
            .register(registry);
    }

    @AfterThrowing(pointcut = "execution(* com.example..*Service.*(..))",
                   throwing = "ex")
    public void recordBusinessError(Throwable ex) {
        bizErrorCounter.increment();
    }
}

这样,业务异常在 Grafana 上单独一张图,告警规则也独立——业务错误率连续 5 分钟 > 1% 就走 P1 告警。这个案例说明:只用 RED 指标是不够的,必须把业务层面的异常也纳入监控体系

Prometheus Pull 模式 vs Push 模式

Prometheus 选择 Pull 模式而非 Graphite/StatsD 的 Push 模式,有三点考量:

  1. 目标状态可感知:如果目标服务挂了,Prometheus 的 Pull 请求直接超时/失败,监控系统能立刻发现服务下线;而 Push 模式下,客户端不推送了,服务端不知道是客户端挂了还是网络问题。
  2. 采集频率统一控制:所有目标的 scrape_interval 由 Prometheus Server 统一管理,不会出现客户端疯狂推送把服务端打爆的情况。
  3. 无中心化瓶颈:不需要额外部署 Agent 或 Collector 集群,每个服务只需暴露一个 /metrics 端点。
yaml
# prometheus.yml 配置
scrape_configs:
  - job_name: 'order-service'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['order-service:8080']
        labels:
          service: 'order-service'
          env: 'prod'
    scrape_interval: 15s
    scrape_timeout: 10s

SLO/SLI/SLA 落地:从口号到可量化

很多团队的口头禅是"高可用 99.9%",但真问起来"你的 99.9% 怎么算的"就说不清了。三个概念必须厘清:

  • SLI(Service Level Indicator):可量化的指标,比如请求延迟 P99 < 200ms、错误率 < 0.1%、可用性 > 99.9%。
  • SLO(Service Level Objective):目标阈值,比如"季度内 99.9% 的请求延迟 P99 < 200ms"。
  • SLA(Service Level Agreement):对外承诺,违反需赔偿,通常比 SLO 更宽松(比如 SLA 承诺 99.9%,SLO 定为 99.95% 留余量)。

Prometheus 中计算 SLO 的典型做法是使用 recording rules 和 alerting rules:

yaml
# Prometheus recording rules
groups:
  - name: slo.rules
    interval: 1m
    rules:
      # 错误率 SLI
      - record: service:error_rate:ratio_5m
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
          /
          sum(rate(http_requests_total[5m]))
      # 延迟 P99 SLI
      - record: service:latency_p99:seconds
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
          )
      # 错误预算消耗(月度 SLO 99.9%)
      - record: service:error_budget_consumed:ratio
        expr: |
          (1 - (sum(rate(http_requests_total{status!~"5.."}[30d]))
                / sum(rate(http_requests_total[30d]))))
          / (1 - 0.999)

Error Budget 消耗计算:真实数据模拟

假设一个服务每天有 1000 万请求,SLO 为 99.9%(季度内),季度总请求量 ≈ 9 亿次。允许的失败请求数 = 9亿 × (1 - 0.999) = 90 万次,对应约 43 分钟的全量不可用。

场景模拟:某天凌晨 2 点发布了一个新版,上线后错误率从 0.01% 升到 0.5%,持续了 2 小时才被发现回滚:

  • 2 小时内错误请求数 = 1000万 × 2/24 × 0.5% ≈ 4167 次
  • 错误预算消耗 = 4167 / 90万 ≈ 0.46%
  • 看起来不多,但如果每周发生一次,季度内累计消耗 ≈ 0.46% × 12 ≈ 5.5%,还安全

但如果发现晚了:错误率 5% 持续 4 小时:

  • 错误请求数 = 1000万 × 4/24 × 5% ≈ 83333 次
  • 单次消耗 = 83333 / 90万 ≈ 9.3%
  • 再来 3 次类似的故障,错误预算烧掉 28%,团队就该叫停发布、全力修稳定性了

这个计算过程在 Grafana 上做一张 Error Budget 燃尽图,每天自动显示剩余预算百分比。当预算消耗 > 50% 时触发 P0 电话告警,> 30% 时触发 P1 IM 告警。

SLO 级别选择:99.9% 还是 99.99%?

SLO 级别季度允许故障时间典型成本倍数适用场景
99.9%≈ 43 分钟1x(基准)大部分内部业务系统、非核心 API
99.95%≈ 21.5 分钟2-3x对外核心 API、支付链路
99.99%≈ 4.3 分钟5-10x实时交易系统、金融核心链路
99.999%≈ 26 秒20-50x极少需要,通常只有基础设施层

建议:核心链路定 99.95%,非核心定 99.9%,不要为了 KPI 定一个 99.99% 的 SLO 然后天天被告警轰炸。每多一个 9,基础设施成本大约翻倍,包含多活部署、异地容灾、全链路冗余、故障演练的投入。

总结

监控体系搭建关键点

  • 四层覆盖:基础设施层(Node Exporter)→ 中间件层(各 Exporter)→ 应用层(RED 指标 + Micrometer)→ 业务层(自定义指标),缺一层都会导致排查盲区。
  • 告警不发噪音:P0(SLO 错误预算消耗 > 50%,电话告警)→ P1(单个服务错误率 > 5% 持续 5 分钟,IM 告警)→ P2(CPU > 80%,第二天处理),不要在凌晨 3 点因为 CPU 过载 80% 打电话叫醒值班人员。
  • SLO 定在 99.9% 还是 99.99%:每多一个 9,基础设施成本大约翻倍。大部分业务 99.9% 已经够用,不要为了 KPI 定一个达不到的 SLO 然后天天被告警轰炸。
  • Error Budget 是管理工具,不是技术指标:季度内 99.9% 的 SLO 允许约 43 分钟故障时间,这 43 分钟是"可消耗的故障额度",预算充足时可以快速迭代,预算快耗尽时叫停发布全力修稳定性。

生产避坑

  • Prometheus 的存储是单机 Time Series Database,默认保留 15 天。超过 1000 万时间序列时查询性能会显著下降,建议用 Thanos 或 VictoriaMetrics 做长期存储和全局查询。
  • 自定义指标标签(label)的基数不能太高——userId 做标签会导致 Prometheus 内存爆炸,因为每个用户都会产生一个新的时间序列。标签的基数建议控制在 1000 以内。
  • 告警规则的 for 字段必须设置,比如 for: 5m 表示"持续 5 分钟异常才告警",避免瞬时的抖动触发误报。
  • 业务错误 vs 技术错误必须分开埋点:否则 HTTP 200 但业务逻辑异常的场景完全漏掉。建议每个业务异常码注册一个单独的 Counter,Grafana 上按业务码聚合出图。
  • Prometheus 的 /metrics 端点不要暴露公网:内网通过负载均衡器访问,或者加 Prometheus 的 basic_auth 配置。曾经有公司把 Prometheus 暴露到公网,被爬虫抓走了所有服务拓扑信息。

参考:Google SRE 书、Prometheus 官方文档、Grafana Labs 博客、Nicolas 的《SRE with Prometheus》

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。