SpringCloud---可观测性与监控:Actuator / Micrometer / Prometheus / Grafana 深度解析

基于:Spring Boot 3.5.x(示例兼容 4.x,配 Micrometer 1.15)/ Prometheus 3.x / Grafana 13.2

「应用内埋点(Actuator + Micrometer)→ 采集存储(Prometheus)→ 可视化告警(Grafana)」主线,拆解每个组件的设计思想与核心机制,剖析一条指标从代码埋点到 Grafana 面板的全链路,最后一章重点讲解 Grafana 的使用实战(数据源、面板、变量、PromQL 配方、告警体系)。


目录

  1. 可观测性问题域与整体架构
    • [1.1 三支柱与组件职责](#1.1 三支柱与组件职责)
    • [1.2 指标数据链路全景](#1.2 指标数据链路全景)
    • [1.3 组件定位表](#1.3 组件定位表)
  2. 组件核心机制
    • [2.1 Actuator:应用状态的统一暴露层](#2.1 Actuator:应用状态的统一暴露层)
    • [2.2 Micrometer:指标门面与三大指标类型 ★★★](#2.2 Micrometer:指标门面与三大指标类型 ★★★)
    • [2.3 Prometheus:拉模型与 PromQL ★★★](#2.3 Prometheus:拉模型与 PromQL ★★★)
  3. [主线流程剖析 ★★★](#主线流程剖析 ★★★)
    • [3.1 全链路五步](#3.1 全链路五步)
    • [3.2 时序与数据延迟](#3.2 时序与数据延迟)
  4. 核心类关系图
  5. [Grafana 使用详解 ★★★](#Grafana 使用详解 ★★★)
    • [5.1 安装与启动](#5.1 安装与启动)
    • [5.2 接入 Prometheus 数据源](#5.2 接入 Prometheus 数据源)
    • [5.3 创建第一个面板](#5.3 创建第一个面板)
    • [5.4 可视化类型与面板配置](#5.4 可视化类型与面板配置)
    • [5.5 模板变量 Variables](#5.5 模板变量 Variables)
    • [5.6 JVM / Spring 指标面板配方](#5.6 JVM / Spring 指标面板配方)
    • [5.7 告警体系:规则 → 触点 → 通知策略](#5.7 告警体系:规则 → 触点 → 通知策略)
    • [5.8 组织、权限与 Provisioning](#5.8 组织、权限与 Provisioning)
    • [5.9 Grafana 常见问题](#5.9 Grafana 常见问题)
  6. 扩展点与常见问题

1. 可观测性问题域与整体架构

1.1 三支柱与组件职责

微服务化后,一次请求可能跨十几个服务,"出问题在哪一环"成了核心难题。可观测性(Observability)通过三个支柱回答三类问题:

支柱 回答的问题 典型技术栈 本文覆盖
日志 Logs 发生了什么(详情) ELK / Loki / 云厂商日志 ❌ 不展开
指标 Metrics 现在是否异常(聚合数字) Actuator + Micrometer + Prometheus + Grafana ✅ 本文主线
追踪 Traces 请求路径与耗时分布 Micrometer Tracing + Zipkin / Tempo / SkyWalking 提及(见 6 章)

指标的价值:低成本、可聚合、可告警。单条日志动辄 KB 级且无法长期存储,指标一行样本十几字节,可存数年、可做阈值告警------所以监控体系(而非排障体系)总是从指标起步。

1.2 指标数据链路全景

#mermaid-svg-3MZk4Nt6bbWEPm5E{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3MZk4Nt6bbWEPm5E .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3MZk4Nt6bbWEPm5E .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3MZk4Nt6bbWEPm5E .error-icon{fill:#552222;}#mermaid-svg-3MZk4Nt6bbWEPm5E .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3MZk4Nt6bbWEPm5E .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3MZk4Nt6bbWEPm5E .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3MZk4Nt6bbWEPm5E .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3MZk4Nt6bbWEPm5E .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3MZk4Nt6bbWEPm5E .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3MZk4Nt6bbWEPm5E .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3MZk4Nt6bbWEPm5E .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3MZk4Nt6bbWEPm5E .marker.cross{stroke:#333333;}#mermaid-svg-3MZk4Nt6bbWEPm5E svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3MZk4Nt6bbWEPm5E p{margin:0;}#mermaid-svg-3MZk4Nt6bbWEPm5E .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3MZk4Nt6bbWEPm5E .cluster-label text{fill:#333;}#mermaid-svg-3MZk4Nt6bbWEPm5E .cluster-label span{color:#333;}#mermaid-svg-3MZk4Nt6bbWEPm5E .cluster-label span p{background-color:transparent;}#mermaid-svg-3MZk4Nt6bbWEPm5E .label text,#mermaid-svg-3MZk4Nt6bbWEPm5E span{fill:#333;color:#333;}#mermaid-svg-3MZk4Nt6bbWEPm5E .node rect,#mermaid-svg-3MZk4Nt6bbWEPm5E .node circle,#mermaid-svg-3MZk4Nt6bbWEPm5E .node ellipse,#mermaid-svg-3MZk4Nt6bbWEPm5E .node polygon,#mermaid-svg-3MZk4Nt6bbWEPm5E .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3MZk4Nt6bbWEPm5E .rough-node .label text,#mermaid-svg-3MZk4Nt6bbWEPm5E .node .label text,#mermaid-svg-3MZk4Nt6bbWEPm5E .image-shape .label,#mermaid-svg-3MZk4Nt6bbWEPm5E .icon-shape .label{text-anchor:middle;}#mermaid-svg-3MZk4Nt6bbWEPm5E .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3MZk4Nt6bbWEPm5E .rough-node .label,#mermaid-svg-3MZk4Nt6bbWEPm5E .node .label,#mermaid-svg-3MZk4Nt6bbWEPm5E .image-shape .label,#mermaid-svg-3MZk4Nt6bbWEPm5E .icon-shape .label{text-align:center;}#mermaid-svg-3MZk4Nt6bbWEPm5E .node.clickable{cursor:pointer;}#mermaid-svg-3MZk4Nt6bbWEPm5E .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3MZk4Nt6bbWEPm5E .arrowheadPath{fill:#333333;}#mermaid-svg-3MZk4Nt6bbWEPm5E .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3MZk4Nt6bbWEPm5E .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3MZk4Nt6bbWEPm5E .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3MZk4Nt6bbWEPm5E .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3MZk4Nt6bbWEPm5E .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3MZk4Nt6bbWEPm5E .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3MZk4Nt6bbWEPm5E .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3MZk4Nt6bbWEPm5E .cluster text{fill:#333;}#mermaid-svg-3MZk4Nt6bbWEPm5E .cluster span{color:#333;}#mermaid-svg-3MZk4Nt6bbWEPm5E div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3MZk4Nt6bbWEPm5E .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3MZk4Nt6bbWEPm5E rect.text{fill:none;stroke-width:0;}#mermaid-svg-3MZk4Nt6bbWEPm5E .icon-shape,#mermaid-svg-3MZk4Nt6bbWEPm5E .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3MZk4Nt6bbWEPm5E .icon-shape p,#mermaid-svg-3MZk4Nt6bbWEPm5E .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3MZk4Nt6bbWEPm5E .icon-shape .label rect,#mermaid-svg-3MZk4Nt6bbWEPm5E .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3MZk4Nt6bbWEPm5E .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3MZk4Nt6bbWEPm5E .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3MZk4Nt6bbWEPm5E :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 可视化层
采集存储层
应用层(每个微服务)
HTTP GET,每 15s
触发告警规则
PromQL 查询
业务代码埋点

Counter / Timer / Gauge
Actuator 端点

/actuator/prometheus
Prometheus Server

定时 scrape 拉取
TSDB 时序库
Alertmanager

告警路由/去重/静默
Grafana

Dashboard / Panel
告警通知

邮件/钉钉/飞书/Webhook

关键认知 :这条链路是拉模型------Prometheus 主动去每个应用"拉"指标,应用无需知道监控系统在哪。Grafana 不存数据,它只是 Prometheus 的"查询前端 + 渲染器"。

1.3 组件定位表

组件 角色 一句话职责
Spring Boot Actuator 暴露层 把应用内部状态(健康、指标、日志级别......)统一暴露成 HTTP/JMX 端点
Micrometer 指标门面 统一指标 API,屏蔽 Prometheus/Influx/OTLP 等后端差异("指标的 SLF4J")
Prometheus 采集 + 存储 + 查询 定时拉取指标存 TSDB,提供 PromQL 查询与告警规则评估
Grafana 可视化 + 告警 多数据源 Dashboard,把 PromQL 结果渲染成图表并触发告警

2. 组件核心机制

2.1 Actuator:应用状态的统一暴露层

Actuator 是 Spring Boot 的"内窥镜",所有能力以**端点(Endpoint)**形式暴露,默认前缀 /actuator

核心端点清单

端点 用途 端点 用途
health 健康检查(探活/就绪) metrics 指标名清单
info 应用自定义信息 prometheus Prometheus 抓取格式
beans Bean 装配报告 loggers 运行时改日志级别
conditions 自动配置条件报告 threaddump 线程转储
configprops 配置属性绑定报告 heapdump 堆转储下载
env 环境变量(可脱敏) scheduledtasks 定时任务清单

启用与暴露配置(默认只暴露 health,必须显式打开):

yaml 复制代码
management:
  endpoints:
    web:
      base-path: /actuator
      exposure:
        include: health,info,metrics,prometheus,loggers   # 按需暴露
        exclude: env,beans                                # 敏感端点显式排除
  endpoint:
    health:
      show-details: always                                # 健康详情(含组件级状态)
      probes:
        enabled: true                                     # k8s 探针需要(/health/liveness /health/readiness)
  metrics:
    tags:
      application: ${spring.application.name}             # 所有指标自动追加 application 标签

安全建议(生产必做):把 Actuator 挪到独立管理端口,不对外暴露:

yaml 复制代码
management:
  server:
    port: 8081          # 业务端口 8080 不暴露监控端点
    address: 127.0.0.1  # 仅本机/Prometheus 可达

自定义端点(把业务状态纳入监控体系):

java 复制代码
@Component
@Endpoint(id = "orderStats")
public class OrderStatsEndpoint {

    private final AtomicLong totalOrders = new AtomicLong();

    // GET /actuator/orderStats
    @ReadOperation
    public Map<String, Object> stats() {
        return Map.of(
            "totalOrders", totalOrders.get(),
            "desc", "订单统计"
        );
    }

    // POST /actuator/orderStats(可选写操作)
    @WriteOperation
    public void reset() {
        totalOrders.set(0);
    }
}

2.2 Micrometer:指标门面与三大指标类型 ★★★

设计思想:门面模式 。如同 SLF4J 之于日志,Micrometer 定义统一 API(MeterRegistry),后端实现可插拔:

类比 日志 指标
门面 API SLF4J Micrometer
后端实现 Logback / Log4j2 PrometheusMeterRegistry / InfluxMeterRegistry / OTLP Registry
切换成本 换一个依赖 换一个 Registry

三大指标类型(含五小类):

类型 语义 典型场景
Counter 只增不减的计数 订单数、错误数、请求数
Gauge 瞬时值(回调函数求值) 队列长度、连接池活跃数、CPU 使用率
Timer 时长 + 计数 + 分位数直方图 接口耗时、DB 查询耗时
DistributionSummary 值分布(非时间) 请求体大小、响应字节数
LongTaskTimer 进行中任务时长 定时任务、批量任务

业务埋点示例MeterRegistry 直接注入即可用):

java 复制代码
@Component
public class OrderMetrics {

    private final Counter orderCounter;
    private final Timer orderLatency;
    private final AtomicInteger pendingOrders = new AtomicInteger();

    public OrderMetrics(MeterRegistry registry) {
        // ① Counter:订单总数,channel 为维度标签
        orderCounter = Counter.builder("orders.total")
                .description("订单总数")
                .tag("channel", "app")
                .register(registry);

        // ② Timer:下单耗时,同时发布 P50/P95/P99 直方图分位
        orderLatency = Timer.builder("order.latency")
                .publishPercentiles(0.5, 0.95, 0.99)
                .publishPercentileHistogram()      // 供 Prometheus histogram_quantile 计算
                .register(registry);

        // ③ Gauge:待处理订单数(回调式,每次 scrape 实时求值)
        Gauge.builder("orders.pending", pendingOrders, AtomicInteger::get)
                .register(registry);
    }

    public void recordOrder(String channel, long costMillis) {
        pendingOrders.incrementAndGet();
        try {
            orderCounter.increment();
            orderLatency.record(costMillis, TimeUnit.MILLISECONDS);
        } finally {
            pendingOrders.decrementAndGet();
        }
    }
}

注解方式@Timed,需注册 TimedAspect 切面):

java 复制代码
@Configuration
public class MetricsConfig {
    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);   // @Timed 生效的前提
    }
}

// 使用:方法级计时,extraTags 追加维度
@Timed(value = "order.create", histogram = true, extraTags = {"biz", "order"})
public Order create(Order order) { ... }

命名约定与标签

  • 名称用 . 分层(orders.totalhttp.server.requests),Prometheus 导出时 ._
  • **Tag(标签)**是 Prometheus 多维模型的基石:orders_total{channel="app"}{channel="wx"} 是两条独立时间序列
  • ⚠️ 基数(Cardinality)红线 :Tag 取值必须是有限小集合 。把 userIdorderId 当 Tag 会造成时间序列爆炸,直接拖垮 Prometheus 内存

Prometheus 导出格式/actuator/prometheus 的文本内容,Boot 4.x 为 OpenMetrics 格式):

复制代码
# HELP orders_total 订单总数
# TYPE orders_total counter
orders_total{application="order-service",channel="app",} 42.0

# HELP order_latency_seconds
# TYPE order_latency_seconds histogram
order_latency_seconds_bucket{application="order-service",le="0.005",} 3.0
order_latency_seconds_bucket{application="order-service",le="0.01",}  8.0
order_latency_seconds_bucket{application="order-service",le="+Inf",}  42.0
order_latency_seconds_count{application="order-service",} 42.0
order_latency_seconds_sum{application="order-service",} 21.36

关键认知 :Micrometer 的 Timer 导出为 Prometheus histogram_bucket/_count/_sum 三组序列),这是后面 P99 面板能算出来的数据基础------le 桶是预先配置的,桶粒度决定分位数精度。

2.3 Prometheus:拉模型与 PromQL ★★★

架构:拉(Pull)模型 。Prometheus Server 按 scrape_interval 周期主动拉取每个 target 的指标端点,写本地 TSDB。对比推模型(Push):应用无感知、天然带健康探测(拉不到即 target down)、适合 k8s 服务发现。

数据模型 :一条样本 = 指标名{标签集} 值 时间戳,如:

复制代码
http_server_requests_seconds_count{application="order-service",method="GET",status="200"} 1023 @1720000000

PromQL 核心语法速查

语法 含义 示例
直接查询 即时向量 jvm_memory_used_bytes{area="heap"}
rate(v[5m]) 5 分钟窗口平均每秒速率(仅限 counter rate(http_server_requests_seconds_count[5m])
irate(v[5m]) 瞬时速率(只看最后两点,毛刺敏感) 适合长周期监控快速变化
increase(v[1h]) 窗口内增量 increase(orders_total[1h]) 每小时订单数
sum by (label) 按标签聚合求和 sum by (application) (rate(...[5m]))
histogram_quantile(φ, ...) 从 histogram 桶推算分位数 histogram_quantile(0.99, sum by (le) (rate(..._bucket[5m])))
topk(k, v) 取前 k topk(5, rate(jvm_gc_pause_seconds_count[5m]))
offset 时间平移对比 rate(...[5m]) offset 1h 与一小时前对比
比较/布尔 阈值过滤 rate(...) > 10

抓取配置prometheus.yml):

yaml 复制代码
global:
  scrape_interval: 15s          # 全局抓取周期
  evaluation_interval: 15s      # 告警规则评估周期

scrape_configs:
  # 静态配置:开发/小规模
  - job_name: 'spring-boot'
    metrics_path: '/actuator/prometheus'
    scrape_interval: 10s
    static_configs:
      - targets:
          - 'order-service:8081'
          - 'stock-service:8081'

  # 服务发现:生产推荐(Consul/Nacos/k8s 自动发现新实例)
  - job_name: 'spring-boot-k8s'
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: "true"
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)

告警规则alerts.yml,由 --alertmanager.url 关联):

yaml 复制代码
groups:
  - name: spring-boot-alerts
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
          /
          sum(rate(http_server_requests_seconds_count[5m])) > 0.05
        for: 2m                      # 持续 2 分钟才触发,防抖动
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.application }} 错误率超过 5%"
          value: "当前错误率 {{ $value | humanizePercentage }}"

一句话概括:Prometheus 的完整闭环 = scrape 采集 → TSDB 存储 → PromQL 查询 → 规则评估 → Alertmanager 去重/分组/路由。


3. 主线流程剖析 ★★★

3.1 全链路五步

一个 http.server.requests 指标从代码到 Grafana 面板,经历五站:
#mermaid-svg-mEI9LxwilwaV0Hp7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mEI9LxwilwaV0Hp7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mEI9LxwilwaV0Hp7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mEI9LxwilwaV0Hp7 .error-icon{fill:#552222;}#mermaid-svg-mEI9LxwilwaV0Hp7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mEI9LxwilwaV0Hp7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mEI9LxwilwaV0Hp7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mEI9LxwilwaV0Hp7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mEI9LxwilwaV0Hp7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mEI9LxwilwaV0Hp7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mEI9LxwilwaV0Hp7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mEI9LxwilwaV0Hp7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mEI9LxwilwaV0Hp7 .marker.cross{stroke:#333333;}#mermaid-svg-mEI9LxwilwaV0Hp7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mEI9LxwilwaV0Hp7 p{margin:0;}#mermaid-svg-mEI9LxwilwaV0Hp7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mEI9LxwilwaV0Hp7 .cluster-label text{fill:#333;}#mermaid-svg-mEI9LxwilwaV0Hp7 .cluster-label span{color:#333;}#mermaid-svg-mEI9LxwilwaV0Hp7 .cluster-label span p{background-color:transparent;}#mermaid-svg-mEI9LxwilwaV0Hp7 .label text,#mermaid-svg-mEI9LxwilwaV0Hp7 span{fill:#333;color:#333;}#mermaid-svg-mEI9LxwilwaV0Hp7 .node rect,#mermaid-svg-mEI9LxwilwaV0Hp7 .node circle,#mermaid-svg-mEI9LxwilwaV0Hp7 .node ellipse,#mermaid-svg-mEI9LxwilwaV0Hp7 .node polygon,#mermaid-svg-mEI9LxwilwaV0Hp7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mEI9LxwilwaV0Hp7 .rough-node .label text,#mermaid-svg-mEI9LxwilwaV0Hp7 .node .label text,#mermaid-svg-mEI9LxwilwaV0Hp7 .image-shape .label,#mermaid-svg-mEI9LxwilwaV0Hp7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-mEI9LxwilwaV0Hp7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mEI9LxwilwaV0Hp7 .rough-node .label,#mermaid-svg-mEI9LxwilwaV0Hp7 .node .label,#mermaid-svg-mEI9LxwilwaV0Hp7 .image-shape .label,#mermaid-svg-mEI9LxwilwaV0Hp7 .icon-shape .label{text-align:center;}#mermaid-svg-mEI9LxwilwaV0Hp7 .node.clickable{cursor:pointer;}#mermaid-svg-mEI9LxwilwaV0Hp7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mEI9LxwilwaV0Hp7 .arrowheadPath{fill:#333333;}#mermaid-svg-mEI9LxwilwaV0Hp7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mEI9LxwilwaV0Hp7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mEI9LxwilwaV0Hp7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mEI9LxwilwaV0Hp7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mEI9LxwilwaV0Hp7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mEI9LxwilwaV0Hp7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mEI9LxwilwaV0Hp7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mEI9LxwilwaV0Hp7 .cluster text{fill:#333;}#mermaid-svg-mEI9LxwilwaV0Hp7 .cluster span{color:#333;}#mermaid-svg-mEI9LxwilwaV0Hp7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mEI9LxwilwaV0Hp7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mEI9LxwilwaV0Hp7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-mEI9LxwilwaV0Hp7 .icon-shape,#mermaid-svg-mEI9LxwilwaV0Hp7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mEI9LxwilwaV0Hp7 .icon-shape p,#mermaid-svg-mEI9LxwilwaV0Hp7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mEI9LxwilwaV0Hp7 .icon-shape .label rect,#mermaid-svg-mEI9LxwilwaV0Hp7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mEI9LxwilwaV0Hp7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mEI9LxwilwaV0Hp7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mEI9LxwilwaV0Hp7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ① 埋点与注册

业务代码 + Spring 自动绑定

(JVM/GC/连接池/HTTP 指标)
② Actuator 暴露

PrometheusScrapeEndpoint

输出 Prometheus 文本格式
③ Prometheus 抓取

scrape → TSDB 存储

(scrape_interval 周期)
④ Grafana 查询

PromQL → Prometheus HTTP API

(/api/v1/query_range)
⑤ 渲染与告警

Panel 图表 / Alert Rule 评估

→ 通知渠道

步骤 关键点 涉及组件
① 埋点与注册 Spring Boot 自动配置通过 MeterBinder 批量绑定 JVM/GC/Logback/数据源/HTTP 指标,业务代码只需 MeterRegistry 埋点 Micrometer
② 暴露 PrometheusMeterRegistry 把内存指标快照序列化为文本,/actuator/prometheus 每次访问实时生成 Actuator
③ 抓取 Server 对每个 target 发 GET /actuator/prometheus,解析后写 TSDB;拉取失败标记 target down Prometheus
④ 查询 Grafana 把面板表达式翻译成 PromQL,走 /api/v1/query_range?start&end&step 批量取回 Prometheus + Grafana
⑤ 渲染 结果转 DataFrame(长表)交给 Panel 渲染;告警规则由 Prometheus(或 Grafana 托管)周期评估 Grafana

3.2 时序与数据延迟

延迟来源 量级
指标到注册表 即时(业务调用时更新)
暴露延迟 0(端点实时生成快照)
抓取延迟 ≤ scrape_interval(默认 15s)
聚合窗口 [5m] 等窗口本身引入的平滑延迟
告警延迟 for: 2m 持续判定 + evaluation_interval

关键认知 :监控数据天然是滞后的(约 scrape_interval + 聚合窗口)。排查"现在这一刻"的问题要靠日志与追踪;指标回答的是"最近 5 分钟趋势如何"。面板上显示的时间戳是抓取时刻而非事件时刻,告警设计也要接受这 1~3 分钟延迟。


4. 核心类关系图

#mermaid-svg-3WLldYJaOJcwa1kO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3WLldYJaOJcwa1kO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3WLldYJaOJcwa1kO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3WLldYJaOJcwa1kO .error-icon{fill:#552222;}#mermaid-svg-3WLldYJaOJcwa1kO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3WLldYJaOJcwa1kO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3WLldYJaOJcwa1kO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3WLldYJaOJcwa1kO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3WLldYJaOJcwa1kO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3WLldYJaOJcwa1kO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3WLldYJaOJcwa1kO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3WLldYJaOJcwa1kO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3WLldYJaOJcwa1kO .marker.cross{stroke:#333333;}#mermaid-svg-3WLldYJaOJcwa1kO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3WLldYJaOJcwa1kO p{margin:0;}#mermaid-svg-3WLldYJaOJcwa1kO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3WLldYJaOJcwa1kO .cluster-label text{fill:#333;}#mermaid-svg-3WLldYJaOJcwa1kO .cluster-label span{color:#333;}#mermaid-svg-3WLldYJaOJcwa1kO .cluster-label span p{background-color:transparent;}#mermaid-svg-3WLldYJaOJcwa1kO .label text,#mermaid-svg-3WLldYJaOJcwa1kO span{fill:#333;color:#333;}#mermaid-svg-3WLldYJaOJcwa1kO .node rect,#mermaid-svg-3WLldYJaOJcwa1kO .node circle,#mermaid-svg-3WLldYJaOJcwa1kO .node ellipse,#mermaid-svg-3WLldYJaOJcwa1kO .node polygon,#mermaid-svg-3WLldYJaOJcwa1kO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3WLldYJaOJcwa1kO .rough-node .label text,#mermaid-svg-3WLldYJaOJcwa1kO .node .label text,#mermaid-svg-3WLldYJaOJcwa1kO .image-shape .label,#mermaid-svg-3WLldYJaOJcwa1kO .icon-shape .label{text-anchor:middle;}#mermaid-svg-3WLldYJaOJcwa1kO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3WLldYJaOJcwa1kO .rough-node .label,#mermaid-svg-3WLldYJaOJcwa1kO .node .label,#mermaid-svg-3WLldYJaOJcwa1kO .image-shape .label,#mermaid-svg-3WLldYJaOJcwa1kO .icon-shape .label{text-align:center;}#mermaid-svg-3WLldYJaOJcwa1kO .node.clickable{cursor:pointer;}#mermaid-svg-3WLldYJaOJcwa1kO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3WLldYJaOJcwa1kO .arrowheadPath{fill:#333333;}#mermaid-svg-3WLldYJaOJcwa1kO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3WLldYJaOJcwa1kO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3WLldYJaOJcwa1kO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3WLldYJaOJcwa1kO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3WLldYJaOJcwa1kO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3WLldYJaOJcwa1kO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3WLldYJaOJcwa1kO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3WLldYJaOJcwa1kO .cluster text{fill:#333;}#mermaid-svg-3WLldYJaOJcwa1kO .cluster span{color:#333;}#mermaid-svg-3WLldYJaOJcwa1kO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3WLldYJaOJcwa1kO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3WLldYJaOJcwa1kO rect.text{fill:none;stroke-width:0;}#mermaid-svg-3WLldYJaOJcwa1kO .icon-shape,#mermaid-svg-3WLldYJaOJcwa1kO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3WLldYJaOJcwa1kO .icon-shape p,#mermaid-svg-3WLldYJaOJcwa1kO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3WLldYJaOJcwa1kO .icon-shape .label rect,#mermaid-svg-3WLldYJaOJcwa1kO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3WLldYJaOJcwa1kO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3WLldYJaOJcwa1kO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3WLldYJaOJcwa1kO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 可视化侧(Grafana)
采集存储侧(Prometheus Server)
应用侧(spring-boot-actuator + micrometer-core)
HTTP GET /actuator/prometheus
/api/v1/query_range
MeterRegistry(接口)
PrometheusMeterRegistry
MeterBinder 自动绑定

Jvm*Metrics / LogbackMetrics

DataSourcePoolMetrics / TomcatMetrics
PrometheusScrapeEndpoint

/actuator/prometheus
CollectorRegistry

(prometheus client 原生注册表)
ScrapeManager

按 job 周期并发拉取
TSDB 时序存储
PromQL Engine
RuleManager

告警规则评估
Prometheus DataSource
Query Runner

PromQL → DataFrame
Panel 渲染引擎
Alert Rule 调度器

关键类 职责一句话
MeterRegistry 指标注册门面:查找/创建/删除 Meter
PrometheusMeterRegistry 门面的 Prometheus 实现,与原生 CollectorRegistry 桥接
MeterBinder 绑定接口:bindTo(registry),自动配置批量注册基础设施指标
PrometheusScrapeEndpoint Actuator 端点,把注册表内容输出为 Prometheus 文本格式
ScrapeManager Prometheus 抓取调度器:并发拉取、超时控制、target 健康状态
PromQL Engine 查询引擎:解析 → 执行 → 返回矩阵
Query Runner Grafana 查询执行器:PromQL 请求、响应解析为 DataFrame

5. Grafana 使用详解 ★★★

Grafana 是最终把指标变成图表与告警 的地方。它不存储业务数据,核心模型:DataSource(数据源)→ Dashboard(面板集合)→ Panel(单个图表)→ Alert(告警规则)。本章按从零到生产可用的顺序展开。

5.1 安装与启动

Docker 一条命令起步(默认端口 3000,初始账号 admin / admin,首次登录强制改密):

bash 复制代码
docker run -d --name grafana -p 3000:3000 \
  -v grafana-data:/var/lib/grafana \
  grafana/grafana:13.2.0

# 验证:http://localhost:3000
# 生产环境建议同时挂载配置目录:
# -v grafana-config:/etc/grafana/provisioning

5.2 接入 Prometheus 数据源

UI 方式Connections → Data sources → Add data source → Prometheus,只需填两项:

配置项 说明
Prometheus server URL http://prometheus:9090 Server 地址(Grafana 容器视角,同 docker 网络用容器名)
Name Prometheus 数据源标识,面板查询引用

填完点 Save & test ,出现 Successfully queried the Prometheus API 即连通。

Provisioning 方式(生产推荐,配置即代码,免 UI 手工操作):

yaml 复制代码
# /etc/grafana/provisioning/datasources/prometheus.yaml
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy              # 由 Grafana 服务端代理请求(浏览器不直连 Prometheus)
    url: http://prometheus:9090
    isDefault: true
    jsonData:
      timeInterval: 15s        # 面板最小步长,建议与 scrape_interval 一致

5.3 创建第一个面板

从零创建一个 QPS 面板(Grafana 13 的流程):

  1. Dashboards → New → New dashboard → Add visualization(或现有面板 Edit
  2. 数据源选 Prometheus
  3. 查询编辑两种模式:
    • Query builder(构建器) :Metric 下拉选 http_server_requests_seconds_count,Label filters 加 application=order-service
    • Code(代码):直接输入 PromQL,生产多用此模式:
promql 复制代码
sum(rate(http_server_requests_seconds_count{application="order-service"}[5m]))
  1. 右侧 Panel options:Title 填「QPS」,Legend 选 {``{instance}}
  2. 右上角点 Run queries 预览,图形出来即成功
  3. Apply 保存面板 → 保存 Dashboard(Dashboards → New → Save,命名 Spring Boot 概览,存入 Folder)

5.4 可视化类型与面板配置

选型一句话:看趋势用 Time series,看单值用 Stat,看占比用 Pie,看明细用 Table

可视化 适用场景 关键配置
Time series 默认折线图:QPS、CPU、内存趋势 Legend 位置/格式、轴单位、Thresholds(超阈值变色)
Stat 大数字:当前错误数、实例数 Value type、颜色阈值、Graph mode(加迷你趋势线)
Gauge 仪表盘:内存使用率、线程池占用率 Min/Max、阈值分档(绿/黄/红)
Bar gauge 横向条形:各实例 CPU 对比 Orientation、展示模式
Table 明细列表:top N 接口耗时 排序、分页
Pie chart 占比:错误分布、流量来源 Labels、Tooltip
Histogram 直方图桶分布:耗时分布 桶宽、颜色
Text 静态说明/Markdown 支持变量插值

常用面板级设置(右侧栏):

  • Legend{``{application}} - {``{instance}} 自定义图例;Sort by 按值排序找热点实例
  • Standard options → Unitpercent (0-100)s (seconds)bytes,让数值可读
  • ThresholdsAdd threshold 设红黄线,配 Thresholds mode: absolute,超阈值图形变色
  • Graph styles → Gradient/Point:填充渐变、点状显示,区分多序列

5.5 模板变量 Variables

变量让一个 Dashboard 服务所有应用 ,而非每应用一份。配置位置:Dashboard settings → Variables → Add variable

变量类型 用法 示例
Query 用数据源查询动态生成选项(最常用) label_values(http_server_requests_seconds_count, application)
Custom 手工逗号分隔固定列表 prod,staging
Constant 固定值(隐藏变量) data_source_name=Prometheus
Interval 时间步长选项 1m,5m,15m
Text box 自由输入 按用户输入过滤
yaml 复制代码
# Query 变量的完整配置(JSON Model 简化)
- name: application
  label: 应用
  type: query
  datasource: Prometheus
  query: label_values(http_server_requests_seconds_count, application)
  multi: true            # 多选
  includeAll: true       # 提供 "All" 选项
  refresh: 1             # 1=切换 dashboard 时刷新

使用:面板 PromQL 中写 $application,多选时 Grafana 自动生成正则 (order-service|stock-service)

promql 复制代码
sum(rate(http_server_requests_seconds_count{application=~"$application"}[5m]))

✅ 三个高频变量模板(照抄即用):

promql 复制代码
# 应用列表
label_values(http_server_requests_seconds_count, application)
# 实例列表(依赖 application 变量联动)
label_values(http_server_requests_seconds_count{application="$application"}, instance)
# 接口列表
label_values(http_server_requests_seconds_count{application="$application"}, uri)

5.6 JVM / Spring 指标面板配方

微服务监控必备面板 (复制 PromQL 即可出图,$application 为 5.5 节变量):

面板 PromQL 类型/单位
QPS sum(rate(http_server_requests_seconds_count{application=~"$application"}[5m])) Time series, short
P99 响应时间 histogram_quantile(0.99, sum by (le) (rate(http_server_requests_seconds_bucket{application=~"$application"}[5m]))) Time series, s
错误率 sum(rate(http_server_requests_seconds_count{application=~"$application",status=~"5.."}[5m])) / sum(rate(http_server_requests_seconds_count{application=~"$application"}[5m])) Time series, percent
JVM 堆内存 sum by (application) (jvm_memory_used_bytes{application=~"$application",area="heap"}) Time series, bytes
GC 耗时 rate(jvm_gc_pause_seconds_sum{application=~"$application"}[5m]) Time series, s
GC 次数 rate(jvm_gc_pause_seconds_count{application=~"$application"}[5m]) Time series
线程数 jvm_threads_live_threads{application=~"$application"} Time series
CPU 使用率 system_cpu_usage{application=~"$application"}(宿主)/ process_cpu_usage(进程) Time series, percent
连接池活跃数 hikaricp_connections_active{application=~"$application"} Time series
连接池等待 hikaricp_connections_pending{application=~"$application"} Stat,告警重点
Tomcat 忙碌线程 tomcat_threads_busy_threads{application=~"$application"} Time series
日志 ERROR 速率 sum(rate(logback_events_total{application=~"$application",level="error"}[5m])) Time series
启动时间 process_uptime_seconds{application=~"$application"} Stat, s

进阶技巧

promql 复制代码
# 按接口聚合的 P99 排行(Table 面板 + Instant 查询)
topk(10, histogram_quantile(0.99,
  sum by (le, uri) (rate(http_server_requests_seconds_bucket[5m]))))

# 环比对比(今天 vs 昨天同一时刻,双序列对比观察)
sum(rate(http_server_requests_seconds_count[5m])) offset 1d

# 灰度发布前后对比(instance 维度拆分)
sum by (instance) (rate(http_server_requests_seconds_count{application="order-service"}[5m]))

5.7 告警体系:规则 → 触点 → 通知策略

Grafana 告警三层模型:Alert Rule(什么算异常)→ Contact Point(发给谁/怎么发)→ Notification Policy(谁该收到什么)

① 创建告警规则Alerting → Alert rules → New alert rule

配置区 填写
Rule name 订单错误率过高
Query(表达式) 数据源 Prometheus,PromQL 同上"错误率"
Reduce / Threshold 输入 A,Last()Is above 0.05(Grafana 10+ 用 Reduce/Threshold 管道)
Set evaluation behavior Evaluate every 1m,for 2m
Labels severity=warningteam=order(供通知策略路由)
Folder / Group 规则归属目录与评估分组

② 配置联系人(Contact point)Alerting → Contact points → Add。以通用 Webhook 为例(钉钉/飞书机器人都走 webhook):

Integration Webhook
URL https://oapi.dingtalk.com/robot/send?access_token=xxx
Message 模板(Go template 语法)
复制代码
【{{ .CommonLabels.severity }}】{{ .CommonLabels.alertname }}
应用:{{ index .CommonLabels "application" }}
详情:{{ range .Alerts }}{{ .Annotations.summary }},当前值 {{ .Annotations.value }}{{ end }}

③ 通知策略Alerting → Notification policies → Default policy。默认策略接兜底触点;再按 label 细分路由(例如 severity=critical 单独发电话/短信,team=order 只发订单组群)。

④ 静默(Silences)Alerting → Silences,发版/维护窗口按标签静默 1 小时,避免告警轰炸。

关键认知:Grafana 告警与 Prometheus 自带告警(2.3 节)二选一即可。团队若已用 Alertmanager 体系,规则放 Prometheus 更内聚;否则 Grafana 托管告警(UI 友好、可带图)是更低门槛的选择。

5.8 组织、权限与 Provisioning

概念 说明
Organization 独立命名空间,数据源/Dashboard/用户完全隔离(多租户)
Team / User Role Viewer (只看)/ Editor (改面板)/ Admin(全管理)三级
Folder 权限 Dashboard 按文件夹授权,Viewer 只读指定业务域
匿名访问 auth.anonymous 开启后生成只读分享链接(大屏/汇报常用)
Provisioning 数据源与 Dashboard 均可用 yaml 代码化管理(5.2 节示例),配合 Git 入库

Grafana 13 新特性(升级参考):Suggested dashboards(按数据源推荐现成 Dashboard,带 Prometheus 兼容性评分)、Dynamic dashboards(变量驱动自适应布局)、Two-way Git Sync(Dashboard 双向 Git 同步,含恢复与审计)。

5.9 Grafana 常见问题

Q1:面板显示 No data,排查顺序?

按链路反查五步:① 数据源 Save & test 是否连通;② Prometheus Status → Targets 该 job 是否 up(App 是否存活、路径 management.endpoints.web.exposure 是否包含 prometheus);③ PromQL 在 Prometheus 自带 Graph 页能否出数据(排除 Grafana 问题);④ 指标名是否写错(Prometheus 中 ._、大小写敏感);⑤ 面板时间范围是否覆盖(Last 5 minutesLast 6 hours)。

Q2:面板数据比实际慢几分钟?

正常。延迟 = scrape_interval + PromQL 窗口平滑,见 3.2 节。不能接受就把 scrape_interval 调到 5s(代价:存储与抓取负载翻倍)。

Q3:变量下拉是空的?

检查 Query 变量表达式中的指标在数据源中是否存在;label_values() 的第一个参数必须是存在的指标名;变量依赖链(instance 依赖 application)要注意层级顺序。

Q4:告警规则评估正常但不发通知?

依次查:规则状态是否 Firing(而非 Pending/Normal)→ 规则是否挂了 Contact point → Notification policy 是否匹配该 label 路由 → Contact point 测试按钮发送是否成功(webhook 网络连通性、机器人 token 有效性)。

Q5:多实例时如何只看一个实例?

面板查询用 instance="192.168.1.10:8081" 过滤,或建立 instance 模板变量(5.5 节模板)在顶部切换。


6. 扩展点与常见问题

6.1 扩展点

扩展方式 示例
应用层 Micrometer SPI:自定义 MeterRegistry/MeterBinder 接入 Influx/OTLP/自研监控后端
应用层 Actuator 自定义端点、HealthIndicator 业务状态端点(2.1 节)、依赖组件健康
采集层 Prometheus exporters node_exporter(主机)、mysqld_exporter(MySQL)、kube-state-metrics(k8s)
采集层 服务发现 relabel k8s/Consul/Nacos 自动发现,标签重写
展示层 Grafana 插件生态 170+ 数据源、120+ 可视化面板
演进方向 OpenTelemetry(OTLP) Boot 4.x 深度集成 OTLP,指标/追踪/日志三支柱统一协议

6.2 常见问题

Q1:指标基数爆炸导致 Prometheus 内存暴涨?

Tag 只放低基数 维度(application/instance/status/uri 顶层)。排查:Prometheus → Status → TSDB Status → Top 10 label names with high cardinality,找到高基数标签后去掉或聚合(uri 含参数时用 uri 而非完整 URL)。

Q2:@Timed 注解加了没数据?

① 是否注册了 TimedAspect(2.2 节)------不加切面注解不生效;② 被代理方法是否 public 且经 Spring 代理调用(同类内部调用失效,与 @Transactional 同理);③ management.endpoints.web.exposure.include 是否含 prometheus。

Q3:P99 数值与实际感受不符?

Histogram 分位数精度取决于桶布局 。Micrometer 默认桶适合毫秒~秒级;若接口是微秒级或分布极宽,用 @Timed(histogram = true) 或自定义 publishPercentileHistogram 桶(DistributionStatisticConfig),Grafana 侧同步用 histogram_quantile 而非均值。

Q4:Prometheus 磁盘增长过快?

--storage.tsdb.retention.time=30d 设保留期;② 降低 scrape_interval;③ 检查高基数标签(Q1);④ 长期存储用 remote_write 转存(VictoriaMetrics/Thanos),本地只留热数据。

Q5:生产环境如何安全暴露 Actuator?

management.server.port 独立端口 + 内网访问(2.1 节);配合 Spring Security 对 /actuator/** 鉴权;敏感端点(env/heapdump/threaddump)不暴露。

Q6:如何把指标和调用链追踪关联起来?

Micrometer Tracing(原 Sleuth 后继)生成 traceId/spanId 时,可配置 exemplars:Prometheus 样本携带 exemplar 指向对应 trace,Grafana 面板开启 Exemplars 后,从 P99 毛刺一键跳转 Tempo/Zipkin 查看具体慢请求链路。


参考资料

相关推荐
lbb 小魔仙1 天前
OpenClaw + cpolar 实战:远程 NAS、分享小游戏、RDP,再配置公网 AI 入口
数据库·人工智能·redis·oracle·prometheus
Rain的Java大神之路2 天前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
zhoupenghui1682 天前
Golang pprof 工具详解:监控、压测与调优实战指南
prometheus·pprof
需要8262 天前
MySQL 索引 B+ 树与“查询为什么变慢了
java·spring boot·spring·spring cloud·tomcat
需要8262 天前
Arthas 一个命令排查线上问题
java·jvm·spring·spring cloud
苏渡苇3 天前
Spring Insight 里如何把 Span 画成瀑布时间线
后端·spring·spring cloud·springboot·监控·apm
叶总没有会3 天前
Nacos—注册中心
spring cloud·微服务
ggaofeng4 天前
prometheus时序数据库
prometheus
苏渡苇5 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring·spring cloud·系统监控·apm
叶总没有会5 天前
微服务--分布式基础
java·spring·spring cloud·微服务