Spring Boot 4教程 / 第 90 节

第9章:Micrometer 与 Observability

本章概述

Spring Boot 4 大幅增强了可观察性(Observability)支持,通过 Micrometer 提供统一的指标、追踪和日志记录能力。

本章重点:

  • ✅ 统一的观察性 API
  • ✅ 分布式追踪改进
  • ✅ 新的 Actuator 端点
  • ✅ 虚拟线程的监控
  • ✅ 与 OpenTelemetry 集成

9.1 统一的观察性 API

9.1.1 Micrometer Observation API

Spring Boot 4 引入了统一的 Observation API,简化了指标和追踪的实现。

项目结构

observability-demo/
├── src/main/java/com/example/observability/
│   ├── ObservabilityApplication.java
│   ├── config/
│   │   ├── ObservabilityConfig.java
│   │   └── MetricsConfig.java
│   ├── service/
│   │   ├── OrderService.java
│   │   └── PaymentService.java
│   ├── controller/
│   │   └── OrderController.java
│   └── observation/
│       ├── CustomObservationHandler.java
│       └── BusinessMetrics.java

1. 配置

application.yml:

spring:
  application:
    name: observability-demo
  
  threads:
    virtual:
      enabled: true

management:
  # Actuator 端点配置
  endpoints:
    web:
      exposure:
        include: '*'  # 暴露所有端点(生产环境应限制)
  
  # 指标配置
  metrics:
    tags:
      application: ${spring.application.name}
      environment: ${ENVIRONMENT:dev}
    distribution:
      percentiles-histogram:
        http.server.requests: true
  
  # 追踪配置
  tracing:
    enabled: true
    sampling:
      probability: 1.0  # 100% 采样(开发环境)
  
  # Prometheus 配置
  prometheus:
    metrics:
      export:
        enabled: true

logging:
  pattern:
    level: '%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]'
  level:
    com.example.observability: DEBUG

ObservabilityConfig.java:

package com.example.observability.config;

import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.aop.ObservedAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Spring Boot 4 - 观察性配置
 */
@Configuration
public class ObservabilityConfig {
    
    /**
     * 启用 @Observed 注解支持
     */
    @Bean
    public ObservedAspect observedAspect(ObservationRegistry registry) {
        return new ObservedAspect(registry);
    }
}

2. 使用 @Observed 注解

OrderService.java:

package com.example.observability.service;

import io.micrometer.observation.annotation.Observed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

/**
 * 订单服务 - 使用 @Observed 自动生成指标和追踪
 */
@Service
public class OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);
    
    private final PaymentService paymentService;
    
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
    
    /**
     * 创建订单 - 自动记录指标和追踪
     */
    @Observed(
        name = "order.create",
        contextualName = "create-order",
        lowCardinalityKeyValues = {"type", "online"}
    )
    public Order createOrder(String userId, BigDecimal amount) {
        log.info("Creating order for user: {}, amount: {}", userId, amount);
        
        // 模拟订单创建
        simulateWork(100);
        
        Order order = new Order(
            generateOrderId(),
            userId,
            amount,
            OrderStatus.PENDING
        );
        
        // 调用支付服务
        boolean paymentSuccess = paymentService.processPayment(order.id(), amount);
        
        if (paymentSuccess) {
            order = order.withStatus(OrderStatus.PAID);
            log.info("Order created successfully: {}", order.id());
        } else {
            order = order.withStatus(OrderStatus.FAILED);
            log.error("Order payment failed: {}", order.id());
        }
        
        return order;
    }
    
    /**
     * 查询订单
     */
    @Observed(
        name = "order.get",
        contextualName = "get-order"
    )
    public Order getOrder(String orderId) {
        log.debug("Getting order: {}", orderId);
        simulateWork(50);
        
        return new Order(
            orderId,
            "user123",
            new BigDecimal("99.99"),
            OrderStatus.PAID
        );
    }
    
    /**
     * 取消订单
     */
    @Observed(
        name = "order.cancel",
        contextualName = "cancel-order",
        lowCardinalityKeyValues = {"reason", "user-requested"}
    )
    public void cancelOrder(String orderId) {
        log.info("Cancelling order: {}", orderId);
        simulateWork(80);
    }
    
    private void simulateWork(long millis) {
        try {
            TimeUnit.MILLISECONDS.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    private String generateOrderId() {
        return "ORD-" + System.currentTimeMillis();
    }
}

/**
 * 订单记录
 */
record Order(
    String id,
    String userId,
    BigDecimal amount,
    OrderStatus status
) {
    public Order withStatus(OrderStatus newStatus) {
        return new Order(id, userId, amount, newStatus);
    }
}

enum OrderStatus {
    PENDING, PAID, FAILED, CANCELLED
}

PaymentService.java:

package com.example.observability.service;

import io.micrometer.observation.annotation.Observed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {
    private static final Logger log = LoggerFactory.getLogger(PaymentService.class);
    
    /**
     * 处理支付 - 自动追踪
     */
    @Observed(
        name = "payment.process",
        contextualName = "process-payment"
    )
    public boolean processPayment(String orderId, BigDecimal amount) {
        log.info("Processing payment for order: {}, amount: {}", orderId, amount);
        
        try {
            // 模拟支付处理
            TimeUnit.MILLISECONDS.sleep(200);
            
            // 90% 成功率
            boolean success = Math.random() > 0.1;
            
            if (success) {
                log.info("Payment successful for order: {}", orderId);
            } else {
                log.error("Payment failed for order: {}", orderId);
            }
            
            return success;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
}

3. 控制器

OrderController.java:

package com.example.observability.controller;

import com.example.observability.service.Order;
import com.example.observability.service.OrderService;
import io.micrometer.observation.annotation.Observed;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.util.Map;

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    private final OrderService orderService;
    
    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }
    
    @PostMapping
    @Observed(name = "http.orders.create")
    public Order createOrder(@RequestBody CreateOrderRequest request) {
        return orderService.createOrder(request.userId(), request.amount());
    }
    
    @GetMapping("/{orderId}")
    @Observed(name = "http.orders.get")
    public Order getOrder(@PathVariable String orderId) {
        return orderService.getOrder(orderId);
    }
    
    @DeleteMapping("/{orderId}")
    @Observed(name = "http.orders.cancel")
    public Map<String, String> cancelOrder(@PathVariable String orderId) {
        orderService.cancelOrder(orderId);
        return Map.of("message", "Order cancelled", "orderId", orderId);
    }
}

record CreateOrderRequest(String userId, BigDecimal amount) {}

9.2 分布式追踪改进

9.2.1 与 OpenTelemetry 集成

pom.xml:

<dependencies>
    <!-- Micrometer Tracing -->
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-tracing-bridge-otel</artifactId>
    </dependency>
    
    <!-- OpenTelemetry Exporter -->
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-otlp</artifactId>
    </dependency>
    
    <!-- Zipkin (可选) -->
    <dependency>
        <groupId>io.zipkin.reporter2</groupId>
        <artifactId>zipkin-reporter-brave</artifactId>
    </dependency>
</dependencies>

application.yml:

management:
  tracing:
    enabled: true
    sampling:
      probability: 1.0
  
  # Zipkin 配置
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans
  
  # OpenTelemetry 配置
  otlp:
    tracing:
      endpoint: http://localhost:4318/v1/traces

9.2.2 自定义追踪

CustomObservationHandler.java:

package com.example.observability.observation;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * 自定义观察处理器
 */
@Component
public class CustomObservationHandler implements ObservationHandler<Observation.Context> {
    private static final Logger log = LoggerFactory.getLogger(CustomObservationHandler.class);
    
    @Override
    public void onStart(Observation.Context context) {
        log.debug("Observation started: {}", context.getName());
    }
    
    @Override
    public void onStop(Observation.Context context) {
        log.debug("Observation stopped: {}, duration: {}ms", 
            context.getName(), 
            System.currentTimeMillis() - context.get("startTime"));
    }
    
    @Override
    public void onError(Observation.Context context) {
        log.error("Observation error: {}", context.getName(), context.getError());
    }
    
    @Override
    public boolean supportsContext(Observation.Context context) {
        return true;
    }
}

9.3 新的 Actuator 端点

9.3.1 常用端点

Spring Boot 4 增强了 Actuator 端点:

端点说明新特性
/actuator/health健康检查虚拟线程状态
/actuator/metrics指标更多虚拟线程指标
/actuator/prometheusPrometheus改进的格式
/actuator/traces追踪信息OpenTelemetry 集成
/actuator/threaddump线程转储虚拟线程支持

9.3.2 自定义健康检查

CustomHealthIndicator.java:

package com.example.observability.health;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class CustomHealthIndicator implements HealthIndicator {
    
    @Override
    public Health health() {
        // 检查虚拟线程状态
        Thread currentThread = Thread.currentThread();
        boolean isVirtual = currentThread.isVirtual();
        
        // 检查系统资源
        Runtime runtime = Runtime.getRuntime();
        long freeMemory = runtime.freeMemory();
        long totalMemory = runtime.totalMemory();
        double memoryUsage = (double) (totalMemory - freeMemory) / totalMemory;
        
        if (memoryUsage > 0.9) {
            return Health.down()
                .withDetail("reason", "High memory usage")
                .withDetail("memoryUsage", String.format("%.2f%%", memoryUsage * 100))
                .build();
        }
        
        return Health.up()
            .withDetail("virtualThreads", isVirtual)
            .withDetail("memoryUsage", String.format("%.2f%%", memoryUsage * 100))
            .withDetail("availableProcessors", runtime.availableProcessors())
            .build();
    }
}

9.4 虚拟线程的监控

9.4.1 虚拟线程指标

VirtualThreadMetrics.java:

package com.example.observability.metrics;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;

@Component
public class VirtualThreadMetrics {
    private final MeterRegistry registry;
    
    public VirtualThreadMetrics(MeterRegistry registry) {
        this.registry = registry;
    }
    
    @PostConstruct
    public void init() {
        // 注册虚拟线程指标
        registry.gauge("jvm.threads.virtual", Tags.empty(), this, 
            VirtualThreadMetrics::getVirtualThreadCount);
        
        registry.gauge("jvm.threads.platform", Tags.empty(), this,
            VirtualThreadMetrics::getPlatformThreadCount);
    }
    
    private double getVirtualThreadCount() {
        return Thread.getAllStackTraces().keySet().stream()
            .filter(Thread::isVirtual)
            .count();
    }
    
    private double getPlatformThreadCount() {
        return Thread.getAllStackTraces().keySet().stream()
            .filter(t -> !t.isVirtual())
            .count();
    }
}

9.5 业务指标

9.5.1 自定义业务指标

BusinessMetrics.java:

package com.example.observability.observation;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

/**
 * 业务指标
 */
@Component
public class BusinessMetrics {
    private final Counter orderCreatedCounter;
    private final Counter orderFailedCounter;
    private final Timer orderProcessingTimer;
    
    public BusinessMetrics(MeterRegistry registry) {
        this.orderCreatedCounter = Counter.builder("orders.created")
            .description("Total orders created")
            .tag("type", "business")
            .register(registry);
        
        this.orderFailedCounter = Counter.builder("orders.failed")
            .description("Total orders failed")
            .tag("type", "business")
            .register(registry);
        
        this.orderProcessingTimer = Timer.builder("orders.processing.time")
            .description("Order processing time")
            .tag("type", "business")
            .register(registry);
    }
    
    public void recordOrderCreated() {
        orderCreatedCounter.increment();
    }
    
    public void recordOrderFailed() {
        orderFailedCounter.increment();
    }
    
    public void recordProcessingTime(long millis) {
        orderProcessingTimer.record(millis, TimeUnit.MILLISECONDS);
    }
    
    public <T> T recordProcessing(java.util.function.Supplier<T> supplier) {
        return orderProcessingTimer.record(supplier);
    }
}

9.6 Prometheus 集成

9.6.1 配置 Prometheus

prometheus.yml:

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'spring-boot-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['localhost:8080']

9.6.2 Grafana 仪表板

常用的 Grafana 查询:

# HTTP 请求率
rate(http_server_requests_seconds_count[1m])

# HTTP 请求延迟 P95
histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[1m]))

# 虚拟线程数量
jvm_threads_virtual

# 订单创建率
rate(orders_created_total[1m])

# 内存使用率
jvm_memory_used_bytes / jvm_memory_max_bytes

9.7 Spring Boot 3 vs Spring Boot 4 对比

特性Spring Boot 3Spring Boot 4
Observation API基础支持完全集成
OpenTelemetry需要额外配置原生支持
虚拟线程监控❌ 无✅ 完整支持
@Observed 注解基础功能增强功能
Actuator 端点标准端点新增虚拟线程端点

9.8 小结

本章我们学习了:

统一的观察性 API

  • @Observed 注解
  • 自动指标和追踪

分布式追踪

  • OpenTelemetry 集成
  • Zipkin 支持

Actuator 端点

  • 新的健康检查
  • 虚拟线程监控

业务指标

  • 自定义指标
  • Prometheus 集成

下一步

下一章我们将学习 Spring for Apache Kafka 升级


导航: