可观测性工程:Micrometer 指标模型与 OTLP 导出

从工程视角深入可观测性,剖析 Micrometer 指标模型与 MeterRegistry 维度设计,讲解 OpenTelemetry 与 OTLP 导出、Prometheus 与 Grafana 大盘,以及应用级自定义指标与告警体系

指标(Metrics)是可观测性的地基:它用最小的成本回答「系统现在到底怎么样」。Micrometer 是 JVM 生态的事实标准指标库,OpenTelemetry 则把指标、日志、链路统一到一套语义与传输协议中。本文聚焦指标工程的完整链条,从 Micrometer 模型、维度设计、OTLP 导出到 Grafana 大盘与告警。

前置基础可先阅读 Java 监控诊断与可观测性 与 日志框架、MDC 与分布式链路追踪。

1. 可观测性三大支柱与指标定位

1.1 三大信号的分工

信号回答的问题数据形态成本
指标 Metrics现在怎么样聚合数值低
日志 Logs发生了什么事件文本中
链路 Traces出在哪一步调用关系高

1.2 指标的价值

指标可以无限期低成本保留、可以跨服务横向对比、可以驱动自动伸缩与告警,因此成为 SRE 告警的事实载体。链路与日志负责事后定位,指标负责事前预警。

2. Micrometer 指标模型

2.1 Meter、MeterId 与 Tag

Micrometer 的核心抽象是 Meter,每个 Meter 由唯一的 MeterId 标识,MeterId 由名称加一组 Tag(键值对)组成。Tag 是指标的多维切片,也是告警聚合的维度。

Meter.Id id = new Meter.Id(
        "http.server.requests",          // 名称
        Tags.of("uri", "/orders",        // 维度标签
                "method", "POST"),
        null, null, Type.TIMER);

2.2 指标类型

类型Micrometer 类语义典型场景
CounterCounter单调递增计数请求总数、错误数
GaugeGauge当前瞬态值队列长度、连接数
TimerTimer耗时分布接口延迟
DistributionSummaryDistributionSummary数值分布响应体大小
LongTaskTimerLongTaskTimer长任务进行中时长批处理耗时

2.3 命名规范与单位

命名三段式:<域>.<对象>.<动作>
  例:http.server.requests、db.pool.active、order.created
单位约定:Timer 以秒为单位,字节类指标带 bytes 后缀
基数约束:Tag 取值必须是有限集合,禁止把 orderId 作为标签

3. MeterRegistry 与多注册表

3.1 CompositeMeterRegistry

应用常需要同时把指标送往多个后端。CompositeMeterRegistry 把多个注册表聚合成一个门面:

CompositeMeterRegistry registry = new CompositeMeterRegistry();
registry.add(new PrometheusMeterRegistry(PrometheusConfig.DEFAULT));
registry.add(new OtlpMeterRegistry(OtlpConfig.DEFAULT, Clock.SYSTEM));

3.2 全局注册表

Spring Boot 中 MeterRegistry 由自动配置注入,且天然是 Composite。业务代码统一注入 MeterRegistry,而不是绑定到具体后端:

@Service
public class OrderMetricsService {

    private final Counter orderCreated;

    public OrderMetricsService(MeterRegistry registry) {
        this.orderCreated = Counter.builder("order.created.total")
                .description("累计创建的订单数")
                .register(registry);
    }

    public void onCreated(String channel) {
        orderCreated.increment();                       // 基本计数
        orderCreated.increment();                       // 或按标签递增
    }
}

3.3 维度设计要点

  • 有限基数:标签值来自枚举或固定集合,绝不使用订单号、用户 ID。
  • 高基数拆走:需要按请求维度分析时改用日志或链路,不要堆标签。
  • 统一命名:跨服务遵循统一前缀,便于大盘聚合。

4. OpenTelemetry 与 OTLP 导出

4.1 OpenTelemetry 集成

OpenTelemetry(OTel)是云原生可观测性的统一标准。通过 OTel SDK 桥接 Micrometer,指标既能被 Prometheus 拉取,也能经 OTLP 推送到 Collector 再分发到任意后端:

# application.yml
management:
  otlp:
    metrics:
      export:
        url: http://otel-collector:4317
        step: 30s
  tracing:
    sampling:
      probability: 0.1

4.2 从 Micrometer 桥接到 OTel

引入 micrometer-registry-otlp 后,Micrometer 指标以 OTLP 协议导出,同时保留 Prometheus 端点:

// 依赖 io.micrometer:micrometer-registry-otlp
// 与 io.micrometer:micrometer-registry-prometheus 可并存
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-otlp</artifactId>
</dependency>
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

4.3 OTLP Collector 分发架构

Java 应用 ──OTLP──> OTel Collector ──> Prometheus / ClickHouse / 云端
    │
    └──/actuator/prometheus──> Prometheus 直接拉取

Collector 承担「接入统一、后端可换」的职责,业务侧只需面向 OTLP 输出,后端的增删不影响应用。

5. Prometheus 采集与 Grafana 大盘

5.1 Scrape 配置

# prometheus.yml
scrape_configs:
  - job_name: order-service
    metrics_path: /actuator/prometheus
    static_configs:
      - targets: ["order-service:8080"]
        labels:
          service: order-service

5.2 RED 与 USE 方法

方法含义监控对象示例指标
RED速率、错误、耗时服务请求请求 QPS、5xx 率、P99 延迟
USE利用率、饱和度、错误基础设施CPU 利用率、队列深度、磁盘错误
// RED 三件套在 Spring 中由 actuator 自动暴露
// http.server.requests 已包含 count、sum、bucket 三个维

5.3 Grafana 面板设计

大盘遵循「顶层概要、中层趋势、底层明细」三层:

第一行:全局 QPS、错误率、P99 延迟(服务健康总览)
第二行:JVM 内存、GC 停顿、线程数、连接池
第三行:各接口分维度趋势、依赖调用延迟、队列积压
// 常用 PromQL 片段
// P99 延迟:histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le))
// 错误率:sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) /
//         sum(rate(http_server_requests_seconds_count[5m]))

6. 应用级自定义指标

6.1 业务埋点设计

业务指标直接反映经营状态,比基础设施指标更贴近用户价值。设计原则是「一个指标回答一个问题」:

@Service
public class PaymentMetrics {

    private final DistributionSummary amountSummary;
    private final Counter failureCounter;

    public PaymentMetrics(MeterRegistry registry) {
        this.amountSummary = DistributionSummary.builder("payment.amount")
                .baseUnit("CNY")
                .publishPercentiles(0.5, 0.95)
                .register(registry);
        this.failureCounter = Counter.builder("payment.failure.total")
                .tag("cause", "unknown")
                .register(registry);
    }
}

6.2 Timer 记录慢调用

Timer 的典型用法是包住一次调用,自动记录耗时分布与计数:

Timer timer = Timer.builder("outbound.http.latency")
        .tag("target", "search-service")
        .publishPercentileHistogram()
        .register(registry);

return timer.record(() -> restTemplate.postForObject(url, req, Resp.class));

6.3 自定义 MeterBinder

需要把第三方组件的内部状态暴露为指标时,实现 MeterBinder 并在启动时绑定:

@Component
public class LiquibaseMetricsBinder implements MeterBinder {

    private final ObjectProvider<DataSource> dataSourceProvider;

    @Override
    public void bindTo(MeterRegistry registry) {
        Gauge.builder("db.pool.active", this, self -> {
                    HikariDataSource ds = (HikariDataSource) dataSourceProvider.getObject();
                    return ds.getHikariPoolMXBean().getActiveConnections();
                })
                .register(registry);
    }
}

7. 告警

7.1 SLO 与告警分级

告警要围绕服务等级目标(SLO)设计,而不是对每个指标都设阈值:

P0 页面级:核心链路错误率超标、主流程不可用
P1 页面级:P99 延迟恶化、容量逼近上限
P2 邮件级:依赖故障、非核心功能降级

7.2 告警规则示例

Prometheus 告警规则避免使用 Go 模板,直接给出可读的标签与注解:

groups:
  - name: business-alerts
    rules:
      - alert: PaymentErrorRateHigh
        expr: rate(payment_failure_total[5m]) > 0.02
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: 支付失败率超过 2%
          description: 近 5 分钟支付失败率异常,需立即排查

      - alert: OrderP99High
        expr: histogram_quantile(0.99,
               sum(rate(http_server_requests_seconds_bucket{uri="/api/v1/orders"}[5m]))
               by (le)) > 1.5
        for: 5m
        labels:
          severity: warning

7.3 告警疲劳与抑制

  • 收敛:同类告警聚合,避免同一故障重复轰炸。
  • 抑制:已知问题窗口内自动静默相关告警。
  • 消除:告警必须附带可执行的排查步骤,否则只是噪音。

8. 总结

主题核心要点
指标模型Meter 由名称与 Tag 组成,类型决定语义
注册表Composite 门面,面向后端多写
标准导出Micrometer 桥接 OTLP,Collector 统一分发
大盘RED 看服务、USE 看资源,三层面板设计
业务埋点一个指标回答一个问题,标签基数受限
告警围绕 SLO 分级,收敛与抑制防疲劳

可观测性工程不是堆工具,而是建立「指标定义 → 标准导出 → 大盘可视化 → 分级告警」的闭环。Micrometer 负责把业务事实变成标准指标,OTLP 负责让指标顺畅流向任意后端,而真正产生价值的是围绕 SLO 设计的那一套告警与复盘机制。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java-enterprise」更多文章

  1. JPMS 模块化:module-info 与 JLink 精简运行时
  2. CDC 数据同步:Debezium 与 Kafka 架构实战
  3. 多租户 SaaS 架构:隔离模型与租户上下文传递