Micrometer案例

wen java案例 3

Micrometer 实战案例详解

案例背景

假设我们要为一个电商订单服务添加可观测性,需要监控:

Micrometer案例

  • 订单创建速率
  • 订单处理耗时
  • 订单状态分布
  • 数据库连接池状态
  • JVM 指标

环境准备

<!-- pom.xml -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-core</artifactId>
    <version>1.12.5</version>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>1.12.5</version>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-jmx</artifactId>
    <version>1.12.5</version>
</dependency>

基础配置

@Configuration
public class MicrometerConfig {
    @Bean
    public MeterRegistry meterRegistry() {
        // 自定义 Registry 组合
        CompositeMeterRegistry compositeRegistry = new CompositeMeterRegistry();
        // Prometheus Registry
        PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(
            PrometheusConfig.DEFAULT
        );
        prometheusRegistry.config().commonTags("application", "order-service");
        // JMX Registry
        JmxMeterRegistry jmxRegistry = new JmxMeterRegistry(
            JmxConfig.DEFAULT, 
            Clock.SYSTEM
        );
        compositeRegistry.add(prometheusRegistry);
        compositeRegistry.add(jmxRegistry);
        // 配置默认标签
        compositeRegistry.config()
            .commonTags("environment", "production")
            .commonTags("region", "cn-north-1");
        return compositeRegistry;
    }
    @Bean
    public PrometheusMeterRegistry prometheusRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }
}

核心业务指标埋点

1 计数器 Counter - 订单量统计

@Service
public class OrderService {
    private final Counter orderCounter;
    private final Counter failedOrderCounter;
    private final MeterRegistry meterRegistry;
    public OrderService(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        // 创建计数器 - 总订单数
        this.orderCounter = Counter.builder("order.total")
            .description("Total number of orders")
            .tags("type", "all")
            .register(meterRegistry);
        // 创建计数器 - 失败订单数
        this.failedOrderCounter = Counter.builder("order.failed")
            .description("Number of failed orders")
            .register(meterRegistry);
    }
    public Order createOrder(OrderRequest request) {
        try {
            // 业务逻辑
            Order order = doCreateOrder(request);
            // 计数器自增
            orderCounter.increment();
            // 按省份统计
            meterRegistry.counter("order.total",
                "region", request.getRegion(),
                "type", request.getOrderType()
            ).increment();
            return order;
        } catch (Exception e) {
            failedOrderCounter.increment();
            throw e;
        }
    }
    /**
     * 测试不同数值范围
     */
    @PostConstruct
    public void init() {
        // 模拟批量创建订单计数(测试用途)
        for (int i = 0; i < 100; i++) {
            orderCounter.increment(1.5); // 支持小数增量
        }
        // 1.5 * 100 = 150
        orderCounter.increment(50);
    }
}

2 仪表盘 Gauge - 实时状态

@Service
public class OrderStatusService {
    private final Map<String, Integer> orderStatusMap = new ConcurrentHashMap<>();
    private final MeterRegistry meterRegistry;
    public OrderStatusService(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        initGuages();
    }
    private void initGuages() {
        // 创建 Gauge - 订单状态数量
        Gauge.builder("order.status.count", orderStatusMap, 
                map -> map.getOrDefault("PENDING", 0))
            .description("Current pending orders")
            .tag("status", "PENDING")
            .register(meterRegistry);
        Gauge.builder("order.status.count", orderStatusMap,
                map -> map.getOrDefault("PROCESSING", 0))
            .description("Current processing orders")
            .tag("status", "PROCESSING")
            .register(meterRegistry);
        // 订单金额统计(无需额外线程)
        Gauge.builder("order.amount.total", this, 
                OrderStatusService::getTotalAmount)
            .description("Total order amount")
            .register(meterRegistry);
    }
    private double getTotalAmount() {
        return totalAmount.get();
    }
    private final AtomicLong totalAmount = new AtomicLong(0);
    public void updateOrderStatus(String orderId, String status) {
        // 更新数量
        orderStatusMap.merge(status, 1, Integer::sum);
        // 更新金额
        totalAmount.addAndGet(getOrderAmount(orderId));
    }
}

3 计时器 Timer - 耗时监控

@Service
public class OrderProcessingService {
    private final Timer orderProcessTimer;
    private final Timer dbQueryTimer;
    private final MeterRegistry meterRegistry;
    public OrderProcessingService(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        // 订单处理计时器
        this.orderProcessTimer = Timer.builder("order.process.time")
            .description("Time taken to process order")
            .publishPercentileHistogram()  // 百分位直方图
            .publishPercentiles(0.5, 0.95, 0.99)  // 特定百分位
            .register(meterRegistry);
        // 数据库查询计时器
        this.dbQueryTimer = Timer.builder("db.query.time")
            .description("Database query execution time")
            .register(meterRegistry);
    }
    // 方式一:手动计时
    public void processOrder(String orderId) {
        Timer.Sample sample = Timer.start(meterRegistry);
        try {
            // 订单处理逻辑
            TimeUnit.MILLISECONDS.sleep(200); // 模拟耗时
            processOrderInternal(orderId);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 记录耗时
            sample.stop(orderProcessTimer);
        }
    }
    // 方式二:使用 runnable/callable
    public void processOrderWithTimer(String orderId) {
        orderProcessTimer.record(() -> {
            // 订单处理逻辑
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
    }
    // 方式三:记录指定持续时间
    public void recordOrderDuration(long durationInMillis) {
        orderProcessTimer.record(durationInMillis, TimeUnit.MILLISECONDS);
    }
}

4 长任务计时器 LongTaskTimer - 长期运行任务

@Service
public class BatchProcessingService {
    private final LongTaskTimer longTaskTimer;
    public BatchProcessingService(MeterRegistry meterRegistry) {
        // 批量处理计时器
        this.longTaskTimer = LongTaskTimer.builder("batch.processing")
            .description("Batch processing task duration")
            .register(meterRegistry);
    }
    public void processBatch(List<Order> orders) {
        // 开始计时
        LongTaskTimer.Sample sample = longTaskTimer.start();
        try {
            // 处理数据 - 模拟长时间运行
            for (Order order : orders) {
                Thread.sleep(100); // 每个订单处理100ms
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 停止计时
            sample.stop();
        }
    }
}

5 分发摘要 DistributionSummary - 分布统计

@Service
public class OrderPriceService {
    private final DistributionSummary orderAmountSummary;
    public OrderPriceService(MeterRegistry meterRegistry) {
        // 订单金额分布
        this.orderAmountSummary = DistributionSummary.builder("order.amount")
            .description("Order amount distribution")
            .baseUnit("CNY")
            .publishPercentileHistogram()
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(meterRegistry);
    }
    public void recordOrderAmount(double amount) {
        orderAmountSummary.record(amount);
        // 按订单类型单独记录
        meterRegistry.summary("order.amount")
            .record(amount);
    }
}

自定义绑定器

@Component
public class CustomMetricsBinder implements MeterBinder {
    @Override
    public void bindTo(MeterRegistry registry) {
        // 定义自定义指标
        AtomicInteger activeSessions = new AtomicInteger(0);
        Gauge.builder("app.active.sessions", activeSessions, AtomicInteger::get)
            .description("Active user sessions")
            .tag("type", "web")
            .register(registry);
        // 自定义功能指标
        registry.counter("app.error.count", "type", "auth").increment();
        // 自定义计时器
        Timer timer = Timer.builder("app.request.latency")
            .tags("method", "get", "endpoint", "/api/order")
            .register(registry);
    }
}

完整监控示例

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    private final MeterRegistry meterRegistry;
    private final OrderService orderService;
    public OrderController(MeterRegistry meterRegistry, OrderService orderService) {
        this.meterRegistry = meterRegistry;
        this.orderService = orderService;
    }
    @PostMapping
    public Order createOrder(@RequestBody OrderRequest request) {
        // 记录请求计数
        meterRegistry.counter("api.requests",
            "method", "POST",
            "endpoint", "/api/orders"
        ).increment();
        Timer.Sample sample = Timer.start(meterRegistry);
        try {
            // 创建订单
            Order order = orderService.createOrder(request);
            // 记录成功状态
            meterRegistry.counter("api.requests.success",
                "endpoint", "/api/orders"
            ).increment();
            return order;
        } catch (Exception e) {
            // 记录异常
            meterRegistry.counter("api.requests.error",
                "endpoint", "/api/orders",
                "error_type", e.getClass().getSimpleName()
            ).increment();
            throw e;
        } finally {
            // 记录耗时
            sample.stop(meterRegistry.timer("api.request.duration",
                "method", "POST",
                "endpoint", "/api/orders"
            ));
        }
    }
}

Spring Boot 集成示例

@SpringBootApplication
@EnableScheduling
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
    @Bean
    public PrometheusMeterRegistry prometheusRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }
    @Bean
    public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
        return registry -> registry.config()
            .commonTags("application", "order-service")
            .commonTags("environment", "production");
    }
    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}

测试示例

@SpringBootTest
class OrderServiceMetricsTest {
    @Autowired
    private MeterRegistry meterRegistry;
    @Test
    void testCounterIncrement() {
        // 获取计数器
        Counter counter = meterRegistry.counter("order.total");
        // 记录增量
        counter.increment(10);
        // 验证计数
        assertEquals(10, counter.count());
    }
    @Test
    void testTimerRecording() {
        Timer timer = meterRegistry.timer("test.timer");
        // 记录耗时
        timer.record(100, TimeUnit.MILLISECONDS);
        timer.record(Duration.ofMillis(200));
        // 验证
        assertEquals(2, timer.count());
        assertTrue(timer.totalTime(TimeUnit.MILLISECONDS) >= 300);
    }
    @Test
    void testSummary() {
        DistributionSummary summary = meterRegistry.summary("test.summary");
        summary.record(10.5);
        summary.record(20.3);
        assertEquals(30.8, summary.totalAmount(), 0.01);
        assertEquals(2, summary.count());
    }
}

Prometheus 输出格式

配置完成后,访问 /actuator/prometheus 可以看到:

# HELP order_total Total number of orders
# TYPE order_total counter
order_total{application="order-service",environment="production",} 150.0
# HELP order_process_time Time taken to process order
# TYPE order_process_time summary
order_process_time_count{quantile="0.5",} 5.0
order_process_time_sum{quantile="0.5",} 1234.0

最佳实践总结

1 命名规范

  • 使用小写字母和点号分隔
  • 前缀表示模块:order.user.db.
  • 后缀表示类型:_total_duration_max

2 标签使用

// 好的标签
timer = meterRegistry.timer("order.processing",
    "status", "success",
    "region", "cn-north"
);
// 避免高基数标签(如订单ID)
// 不要使用订单ID作为标签!

3 性能考虑

// 缓存 Meter 实例,避免重复创建
private final Timer timer;
// 在构造器中创建
// 使用定时上报而不是每次请求都上报
@Scheduled(fixedDelay = 60000)
public void reportMetrics() {
    // 定期上报指标
}

4 监控告警

@EventListener
public void handleOrderFailedEvent(OrderFailedEvent event) {
    // 触发告警
    if (failedOrderCounter.count() > 100) {
        // 发送告警通知
        alertService.sendAlert("订单失败次数超过100");
    }
}

十一、常见问题处理

// 处理线程安全
private final AtomicInteger concurrentOrders = new AtomicInteger(0);
@PostConstruct
public void setup() {
    Gauge.builder("order.concurrent", concurrentOrders, AtomicInteger::get)
        .register(meterRegistry);
}
// 动态标签(避免)
public void recordOrderMeter(String orderId) {
    // 不要这样使用,会导致高基数
    meterRegistry.counter("order.detail", "orderId", orderId).increment();
    // 应该使用固定标签
    meterRegistry.counter("order.detail").increment();
}

这个案例涵盖了 Micrometer 的大部分核心功能,你可以根据实际需求选择使用,记得根据监控系统的要求配置对应的 Registry。

抱歉,评论功能暂时关闭!