可观测性三支柱

系统出问题,最怕的是「不知道哪里出了什么问题」。可观测性(Observability)就是回答这个问题的一套工程实践,通常讲三个支柱:Metrics(指标)、Logs(日志)、Traces(链路追踪)。指标回答「系统现在什么状态」,日志回答「发生了什么」,链路回答「一次请求经历了什么」。

三者各有分工:指标适合做告警和趋势分析,比如 CPU 使用率、请求延迟、错误率;日志适合查细节,比如某次异常堆栈;链路适合定位慢请求的瓶颈。生产环境三个都要有,但切入点往往从指标开始,因为指标最轻、最容易规模化。

Micrometer 就是 Java 世界做指标这一支柱的标准化库。它像一个适配层:应用只跟 Micrometer 打交道,至于指标最后送到 Prometheus、InfluxDB 还是 Datadog,通过不同的 Registry 实现切换,代码不用改。

Spring Boot 从 2.x 开始内置 Micrometer,配好依赖和端点就能暴露指标,几乎零成本接入。这也是它成为事实标准的原因:不绑架监控后端,插拔式集成。

可观测性不是监控工具的堆砌,而是「系统状态可被回答」的能力。
本文核心知识点
  • MeterRegistry指标注册与聚合入口。
  • Counter只增不减的计数器。
  • Timer耗时与频率统计。
  • Prometheus标准指标后端。
Micrometer 监控配图:蓝调渐变背景上多层同心圆环与波浪数据面板的抽象科技插画
指标像仪表盘上的指针,随时告诉你系统状态。

Micrometer 核心概念

Micrometer 的核心对象是 MeterRegistry。所有指标(Meter)都从它创建。指标又分几种类型:Counter(计数器)、Gauge(瞬时值)、Timer(计时)、DistributionSummary(分布统计)。每个指标可以带多个 Tag(标签),Tag 用来做维度切分。

import io.micrometer.core.instrument.*;

MeterRegistry registry = new SimpleMeterRegistry();

// 计数器:只增不减
Counter orders = Counter.builder("shop.orders")
    .tag("channel", "app")
    .register(registry);
orders.increment();

// 计时器:统计耗时
Timer timer = Timer.builder("shop.checkout")
    .tag("pay", "wechat")
    .register(registry);
timer.record(() -> doCheckout());

Tag 是 Micrometer 的灵魂。同样是订单数,带上 channel=app、channel=web 就能分别看各渠道。但 Tag 不能滥用,取值太多会让指标基数爆炸,一般建议标签取值控制在几十以内。

接入 Prometheus

Spring Boot 项目接入 Prometheus 只需加一个依赖,启动后 /actuator/prometheus 端点就自动暴露指标。

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

非 Spring 项目手动建一个 PrometheusMeterRegistry,把指标注册进去,再用 SimpleMeterRegistry 的输出格式推给 Prometheus 抓取即可。Prometheus 用 pull 模型,定期来端点抓数据,因此端点要稳定可访问。

PrometheusMeterRegistry registry = new PrometheusMeterRegistry(
    PrometheusConfig.DEFAULT);
registry.scrape();   // 返回 Prometheus 文本格式的指标

采集链路通常是:应用暴露指标端点,Prometheus 定时抓取,Grafana 连接 Prometheus 画图。三件套在 K8s 环境里几乎成了标配。

常用指标类型

四类 Meter 覆盖大部分场景:Counter 统计次数(订单数、错误数),Gauge 记录瞬时值(当前线程数、队列长度),Timer 统计耗时分布(接口 P99 延迟),DistributionSummary 统计值分布(响应体大小)。

// Gauge:瞬时值
AtomicInteger queueSize = new AtomicInteger(0);
Gauge.builder("task.queue.size", queueSize,
        AtomicInteger::get).register(registry);

// Summary:分布统计
DistributionSummary summary = DistributionSummary
    .builder("http.response.bytes")
    .register(registry);
summary.record(1024);

// Timer 带百分位
Timer httpTimer = Timer.builder("http.server.requests")
    .publishPercentiles(0.5, 0.95, 0.99)
    .register(registry);

P99 这类百分位指标对排查「尾部延迟」特别有用:平均延迟正常,但 P99 飙高,说明少数请求拖慢了整体体验。这在监控面板上一定要有。