基于: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.2 指标数据链路全景](#1.2 指标数据链路全景)
- [1.3 组件定位表](#1.3 组件定位表)
- 组件核心机制
- [2.1 Actuator:应用状态的统一暴露层](#2.1 Actuator:应用状态的统一暴露层)
- [2.2 Micrometer:指标门面与三大指标类型 ★★★](#2.2 Micrometer:指标门面与三大指标类型 ★★★)
- [2.3 Prometheus:拉模型与 PromQL ★★★](#2.3 Prometheus:拉模型与 PromQL ★★★)
- [主线流程剖析 ★★★](#主线流程剖析 ★★★)
- [3.1 全链路五步](#3.1 全链路五步)
- [3.2 时序与数据延迟](#3.2 时序与数据延迟)
- 核心类关系图
- [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 常见问题)
- 扩展点与常见问题
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.total、http.server.requests),Prometheus 导出时.→_ - **Tag(标签)**是 Prometheus 多维模型的基石:
orders_total{channel="app"}与{channel="wx"}是两条独立时间序列 - ⚠️ 基数(Cardinality)红线 :Tag 取值必须是有限小集合 。把
userId、orderId当 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 的流程):
Dashboards → New → New dashboard → Add visualization(或现有面板Edit)- 数据源选
Prometheus - 查询编辑两种模式:
- Query builder(构建器) :Metric 下拉选
http_server_requests_seconds_count,Label filters 加application=order-service - Code(代码):直接输入 PromQL,生产多用此模式:
- Query builder(构建器) :Metric 下拉选
promql
sum(rate(http_server_requests_seconds_count{application="order-service"}[5m]))
- 右侧
Panel options:Title 填「QPS」,Legend 选{``{instance}} - 右上角点 Run queries 预览,图形出来即成功
- 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 → Unit :
percent (0-100)、s (seconds)、bytes,让数值可读 - Thresholds :
Add 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=warning、team=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 minutes 换 Last 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 查看具体慢请求链路。