Spring Boot: 可观测性 Observability
最后更新:2026-08-26
可观测性是生产运维的眼睛——指标看趋势、日志找原因、链路追踪定位根因,三支柱缺一不可。
1. 你将学到
- Micrometer 指标收集:Counter / Gauge / Timer / DistributionSummary
- Prometheus 抓取配置与 PromQL 查询告警规则
- Grafana Dashboard 可视化:JVM / HTTP / 数据库指标看板
- OpenTelemetry 分布式链路追踪与 Span/Context 传播
- Bob 建立 OrderFlow 的 SLO 看板:P99 < 100ms / 错误率 < 0.1% / 可用性 > 99.9%
2. 一个 SRE 工程师的真实故事
(1) 痛点:线上问题黑盒排查
OrderFlow 线上报错,Bob 只能看应用日志大海捞针:哪个接口慢?哪个服务出了问题?请求经过了哪些微服务?一概不知。Alice 和 Bob 经常花 4 小时排查一个线上问题,其中 3 小时在"猜"问题在哪。
(2) Observability 三支柱的解法
graph LR
A["Observability<br/>Three Pillars"] --> B["Metrics<br/>What happened?<br/>Prometheus"]
A --> C["Logs<br/>Why happened?<br/>ELK / Loki"]
A --> D["Traces<br/>Where happened?<br/>Jaeger / Zipkin"]
指标发现问题,日志分析原因,链路追踪定位根因。
(3) 收益
Bob 建立 Observability 体系后,Grafana 看板实时展示 P99 延迟和错误率,Prometheus 告警自动通知,OpenTelemetry 链路追踪 30 秒定位问题根因。平均故障排查时间从 4 小时降到 15 分钟。
3. Micrometer 指标收集
(1) 四种指标类型
| 类型 | 含义 | 只增不减 | 典型场景 |
|---|---|---|---|
| Counter | 计数器(只增) | 是 | 请求总数、订单创建数 |
| Gauge | 当前值(可增可减) | 否 | 当前连接数、队列长度 |
| Timer | 耗时分布 | 否 | 请求响应时间 |
| DistributionSummary | 分布统计 | 否 | 请求体大小分布 |
▶ 示例: 自定义业务指标
JAVA
@Service
public class OrderMetrics {
private final Counter orderCreatedCounter;
private final Counter orderCancelledCounter;
private final Timer orderCreationTimer;
private final Gauge pendingOrdersGauge;
public OrderMetrics(MeterRegistry registry, OrderRepository orderRepo) {
this.orderCreatedCounter = Counter.builder("orderflow.orders.created")
.description("Total orders created")
.tag("service", "orderflow")
.register(registry);
this.orderCancelledCounter = Counter.builder("orderflow.orders.cancelled")
.description("Total orders cancelled")
.register(registry);
this.orderCreationTimer = Timer.builder("orderflow.orders.creation.duration")
.description("Order creation duration")
.publishPercentiles(0.5, 0.95, 0.99)
.publishPercentileHistogram()
.register(registry);
this.pendingOrdersGauge = Gauge.builder("orderflow.orders.pending",
orderRepo, repo -> repo.countByStatus("PENDING"))
.description("Current pending orders count")
.register(registry);
}
public void recordOrderCreated() {
orderCreatedCounter.increment();
}
public Timer.Sample startCreationTimer() {
return Timer.start(orderCreationTimer);
}
public void recordCreationComplete(Timer.Sample sample) {
sample.stop(orderCreationTimer);
}
}
输出:
TEXT
📖 仅展示
// 执行成功
▶ 示例: 在 Service 中使用指标
JAVA
@Service
public class OrderServiceImpl implements OrderService {
private final OrderMetrics metrics;
@Override
@Transactional
public Order createOrder(CreateOrderRequest request) {
Timer.Sample sample = metrics.startCreationTimer();
try {
Order order = doCreateOrder(request);
metrics.recordOrderCreated();
return order;
} finally {
metrics.recordCreationComplete(sample);
}
}
}
输出:
TEXT
📖 仅展示
// 执行成功
4. Prometheus 集成
▶ 示例: Prometheus 依赖与配置
XML
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
输出:
TEXT
📖 仅展示
// 执行成功
YAML
# application.yml
management:
endpoints:
web:
exposure:
include: health,prometheus,metrics
metrics:
tags:
application: ${spring.application.name}
export:
prometheus:
enabled: true
▶ 示例: Prometheus 抓取配置
YAML
# prometheus.yml
scrape_configs:
- job_name: 'orderflow'
metrics_path: '/actuator/prometheus'
scrape_interval: 15s
static_configs:
- targets: ['orderflow:8080']
输出:
TEXT
📖 仅展示
Monitoring config loaded
Prometheus targets: 3 active
Grafana dashboard: ready
▶ 示例: PromQL 查询与告警
YAML
# PromQL queries
# P99 latency for order API
histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket{uri=~"/api/v1/orders.*"}[5m])) by (le, uri))
# Error rate (5xx responses)
sum(rate(http_server_requests_seconds_total{status=~"5.."}[5m]))
/
sum(rate(http_server_requests_seconds_total[5m]))
# Orders per minute
rate(orderflow_orders_created_total[1m]) * 60
输出:
TEXT
📖 仅展示
Configuration applied successfully
YAML
# alert_rules.yml
groups:
- name: orderflow
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_server_requests_seconds_total{status=~"5.."}[5m]))
/ sum(rate(http_server_requests_seconds_total[5m])) > 0.001
for: 5m
labels:
severity: critical
annotations:
summary: "OrderFlow error rate exceeds 0.1%"
- alert: HighP99Latency
expr: |
histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le)) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "OrderFlow P99 latency exceeds 100ms"
5. Grafana Dashboard
(1) 关键看板指标
| 面板 | 指标 | PromQL |
|---|---|---|
| 请求速率 | QPS | sum(rate(http_server_requests_seconds_total[5m])) |
| P50/P95/P99 延迟 | 响应时间分布 | histogram_quantile(0.99, ...) |
| 错误率 | 5xx 比例 | rate(...{status=~"5.."})/rate(...) |
| JVM 堆内存 | 内存使用 | jvm_memory_used_bytes{area="heap"} |
| GC 暂停 | GC 耗时 | rate(jvm_gc_pause_seconds_sum[5m]) |
| HikariCP 活跃连接 | 连接池使用 | hikaricp_connections_active |
| 订单创建速率 | 业务指标 | rate(orderflow_orders_created_total[1m]) |
▶ 示例: Grafana Dashboard JSON 片段
JSON
{
"dashboard": {
"title": "OrderFlow Observability",
"panels": [
{
"title": "Request Rate (QPS)",
"type": "timeseries",
"targets": [{
"expr": "sum(rate(http_server_requests_seconds_total{application=\"orderflow-service\"}[5m]))"
}]
},
{
"title": "P99 Latency",
"type": "timeseries",
"targets": [{
"expr": "histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket{application=\"orderflow-service\"}[5m])) by (le))"
}]
},
{
"title": "Error Rate",
"type": "gauge",
"targets": [{
"expr": "sum(rate(http_server_requests_seconds_total{application=\"orderflow-service\",status=~\"5..\"}[5m])) / sum(rate(http_server_requests_seconds_total{application=\"orderflow-service\"}[5m]))"
}],
"fieldConfig": {
"defaults": {
"thresholds": {
"steps": [
{"value": 0, "color": "green"},
{"value": 0.001, "color": "red"}
]
}
}
}
}
]
}
}
输出:
JSON
{
"dashboard": {
"title": "OrderFlow Observability",
"panels": [
{
"title": "Request Rate (QPS)",
"type": "timeseries",
"targets": [
{
"expr": "sum(rate(http_server_requests_seconds_total{application=\"orderflow-service\"}[5m]))"
}
]
},
{
"title": "P99 Latency",
"type": "timeseries",
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(http_server_request
6. OpenTelemetry 链路追踪
(1) 分布式追踪概念
graph LR
A["Client"] --> B["API Gateway<br/>Trace: abc123<br/>Span 1"]
B --> C["Order Service<br/>Span 2<br/>parent: Span 1"]
C --> D["Product Service<br/>Span 3<br/>parent: Span 2"]
C --> E["Database<br/>Span 4<br/>parent: Span 2"]
| 概念 | 含义 |
|---|---|
| Trace | 一次请求的完整链路 |
| Span | 链路中的一个操作 |
| Context | Span 间传递的追踪上下文 |
| SpanId | 当前 Span 唯一标识 |
| TraceId | 整个 Trace 唯一标识 |
▶ 示例: OpenTelemetry 依赖
XML
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>
输出:
TEXT
📖 仅展示
// 执行成功
YAML
# application.yml
otel:
exporter:
otlp:
endpoint: http://otel-collector:4317
resource:
attributes:
service.name: orderflow-service
traces:
exporter: otlp
▶ 示例: 自定义 Span
JAVA
@Service
public class OrderService {
private final Tracer tracer;
public OrderService(Tracer tracer) {
this.tracer = tracer;
}
public Order createOrder(CreateOrderRequest request) {
Span span = tracer.spanBuilder("create-order")
.setAttribute("product.id", request.productId())
.setAttribute("quantity", request.quantity())
.startSpan();
try (Scope scope = span.makeCurrent()) {
Order order = doCreateOrder(request);
span.setAttribute("order.id", order.getId());
return order;
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
span.end();
}
}
}
输出:
TEXT
📖 仅展示
// 执行成功
7. 综合示例:OrderFlow SLO 看板
YAML
# docker-compose.observability.yml
version: "3.9"
services:
prometheus:
image: prom/prometheus:latest
ports: ["9090:9090"]
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- ./alert_rules.yml:/etc/prometheus/alert_rules.yml
grafana:
image: grafana/grafana:latest
ports: ["3000:3000"]
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
volumes:
- grafana-data:/var/lib/grafana
otel-collector:
image: otel/opentelemetry-collector:latest
ports: ["4317:4317", "4318:4318"]
volumes:
- ./otel-collector-config.yml:/etc/otelcol/config.yaml
jaeger:
image: jaegertracing/all-in-one:latest
ports: ["16686:16686"]
app:
build: .
ports: ["8080:8080"]
environment:
OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4317
OTEL_SERVICE_NAME: orderflow-service
MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: health,prometheus,metrics
| SLO 指标 | 目标 | 告警阈值 | PromQL |
|---|---|---|---|
| P99 延迟 | < 100ms | > 100ms 持续 5 分钟 | histogram_quantile(0.99, ...) |
| 错误率 | < 0.1% | > 0.1% 持续 5 分钟 | rate(5xx)/rate(all) |
| 可用性 | > 99.9% | < 99.9% 持续 5 分钟 | 1 - rate(5xx)/rate(all) |
| 订单吞吐 | > 1000/min | < 500/min 持续 10 分钟 | rate(orders_created)[1m]*60 |
❓ 常见问题
Q Metrics 和 Logs 有什么区别?
A Metrics 是聚合数值(计数器、直方图),适合监控趋势和告警。Logs 是离散事件记录,适合分析具体原因。发现问题看 Metrics,分析原因看 Logs。
Q Prometheus 和 Grafana 的分工是什么?
A Prometheus 负责数据采集和存储(时序数据库)+ 告警规则。Grafana 负责可视化展示(Dashboard)。两者配合使用,Prometheus 是数据源,Grafana 是展示层。
Q OpenTelemetry 和 Jaeger 有什么关系?
A OpenTelemetry 是"采集"标准(SDK + API),Jaeger 是"存储和展示"后端。应用用 OTel SDK 采集 Trace 数据,发送到 Jaeger 存储和查询。
Q publishPercentiles 和 histogram_quantile 有什么区别?
A publishPercentiles 在应用端计算分位数(节省 Prometheus 存储,但多实例不精确)。histogram_quantile 在 Prometheus 端计算(支持多实例聚合,更精确)。生产环境推荐 Prometheus 端计算。
Q SLO 和 SLA 有什么区别?
A SLO(Service Level Objective)是内部目标,如 P99 < 100ms。SLA(Service Level Agreement)是对客户的承诺,附带违约赔偿。SLO 是 SLA 的基础。
Q 如何选择日志后端?
A ELK(Elasticsearch + Logstash + Kibana)功能强大但资源消耗高;Loki(Grafana 生态)轻量但查询能力有限。中小项目推荐 Loki,大型项目推荐 ELK。
📖 小节
- Micrometer 四种指标:Counter(计数)、Gauge(当前值)、Timer(耗时)、DistributionSummary(分布)
- Prometheus 抓取
/actuator/prometheus,PromQL 查询 + 告警规则 - Grafana Dashboard 展示 HTTP/JVM/连接池/业务指标
- OpenTelemetry 采集分布式链路,Jaeger 存储和可视化
- SLO 看板:P99 < 100ms / 错误率 < 0.1% / 可用性 > 99.9%
- 三支柱协作:指标发现问题 → 链路定位位置 → 日志分析根因
📝 作业
-
基础题(难度⭐):为 OrderFlow 配置 Micrometer + Prometheus,暴露
/actuator/prometheus端点,编写 3 个自定义业务指标(订单创建计数、下单耗时、待处理订单数)。 -
进阶题(难度⭐⭐):配置 Prometheus 抓取 + Grafana Dashboard,展示 HTTP QPS、P99 延迟、错误率、JVM 堆内存。编写 2 条 Prometheus 告警规则(高错误率、高延迟)。
-
挑战题(难度⭐⭐⭐):集成 OpenTelemetry + Jaeger,实现分布式链路追踪,在 OrderService 中添加自定义 Span,追踪完整的下单链路(Controller → Service → Repository),在 Jaeger UI 中查看 Trace 详情。