Micrometer 自定义业务指标详解
在实际生产环境中,除了 JVM、HTTP、数据库等系统级指标外,业务指标(Business Metrics)是监控系统健康、评估业务健康度、实现可观测性的核心。Micrometer 提供了灵活的 API 和模式,帮助开发者轻松暴露自定义业务指标。
本文将全面详解 如何使用 Micrometer 实现自定义业务指标,涵盖设计原则、实现方式、最佳实践和常见场景。
一、什么是业务指标?
业务指标是反映应用核心业务逻辑运行状态的度量数据,例如:
| 指标类型 | 示例 |
|---|---|
| 交易类 | 订单创建数、支付成功率、退款率 |
| 用户类 | 活跃用户数、注册转化率、登录失败次数 |
| 营销类 | 优惠券发放数、核销率、活动参与人数 |
| 风控类 | 风险交易数、黑名单命中次数 |
| 消息类 | 消息发送量、消费延迟、失败重试次数 |
✅ 业务指标 = 业务价值 + 可观测性
二、为什么需要自定义业务指标?
| 问题 | 解决方案 |
|---|---|
| 系统指标无法反映业务健康度 | 添加订单成功率、支付延迟等 |
| 故障排查缺乏上下文 | 结合 traceId 和业务指标定位问题 |
| 无法评估功能效果 | 通过指标分析新功能转化率 |
| 运维与业务脱节 | 提供业务可读的监控面板 |
三、Micrometer 支持的指标类型回顾
| 类型 | 用途 | 示例 |
|---|---|---|
Counter | 单调递增计数 | 订单创建数、登录失败次数 |
Gauge | 当前瞬时值 | 待处理订单数、库存余额 |
Timer | 执行时间 | 支付耗时、风控校验时间 |
DistributionSummary | 数值分布 | 订单金额分布、优惠券面额 |
LongTaskTimer | 长任务耗时 | 批量导出、对账任务 |
FunctionCounter/Gauge | 基于对象自动采集 | 消息队列积压数 |
四、实现方式详解
方式 1:使用 MeterRegistry 直接注册(简单场景)
@Service
public class OrderService {
private final MeterRegistry meterRegistry;
private final Counter orderCreatedCounter;
private final Timer paymentTimer;
public OrderService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
// 初始化 Counter
this.orderCreatedCounter = meterRegistry.counter(
"orders.created", "status", "success");
// 初始化 Timer
this.paymentTimer = Timer.builder("payment.duration")
.tag("method", "wechat")
.register(meterRegistry);
}
public void createOrder() {
// 业务逻辑...
orderCreatedCounter.increment();
}
public void pay() {
paymentTimer.record(() -> {
// 支付逻辑
});
}
}
✅ 适合简单、固定指标
❌ 缺点:指标分散,不易复用
方式 2:使用 MeterBinder(推荐 ✅)
将业务指标封装为独立组件,符合关注点分离原则。
@Component
public class OrderMetricsBinder implements MeterBinder {
private final OrderRepository orderRepository;
private final AtomicInteger pendingOrders = new AtomicInteger(0);
public OrderMetricsBinder(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Override
public void bindTo(MeterRegistry registry) {
// 1. Gauge:当前待处理订单数(FunctionGauge)
FunctionGauge.builder("orders.pending", this, binder ->
binder.orderRepository.countByStatus("PENDING"))
.description("待处理订单数量")
.register(registry);
// 2. Counter:订单创建总数
Counter successCounter = registry.counter("orders.created", "result", "success");
Counter failedCounter = registry.counter("orders.created", "result", "failed");
// 3. DistributionSummary:订单金额分布
DistributionSummary amountSummary = DistributionSummary.builder("order.amount")
.baseUnit("rmb")
.publishPercentiles(0.5, 0.95, 0.99)
.register(registry);
// 注册业务回调(可通过事件或监听器触发)
orderRepository.setOnOrderCreated(order -> {
if (order.isSuccess()) {
successCounter.increment();
} else {
failedCounter.increment();
}
amountSummary.record(order.getAmount());
pendingOrders.incrementAndGet();
});
orderRepository.setOnOrderProcessed(order -> {
pendingOrders.decrementAndGet();
});
}
}
✅ 推荐:职责清晰、可复用、易于测试
方式 3:使用注解 @Counted / @Timed(AOP)
适用于方法级监控。
@Service
public class PaymentService {
@Counted(value = "payments.attempted", description = "支付尝试次数")
@Timed(value = "payment.duration",
publishPercentileHistogram = true,
extraTags = {"type", "wechat"})
public PaymentResult pay(Order order) {
// 支付逻辑
}
@Counted(value = "logins.failed", recordFailuresOnly = true)
public void login(String username, String password) {
if (!authService.validate(username, password)) {
throw new LoginFailedException();
}
}
}
✅ 适合通用方法监控
❌ 不适合复杂聚合逻辑
五、常见业务指标场景与实现
场景 1:订单状态统计(Gauge + Counter)
@Bean
public MeterBinder orderStatusMetrics(OrderService orderService) {
return registry -> {
// 当前各状态订单数
Arrays.asList("CREATED", "PAID", "SHIPPED", "COMPLETED").forEach(status -> {
FunctionGauge.builder("orders.count", orderService, svc ->
svc.countByStatus(status))
.tag("status", status.toLowerCase())
.register(registry);
});
// 订单创建总数(带结果标签)
Counter.builder("orders.created")
.tags("result", "success")
.register(registry);
};
}
场景 2:优惠券核销率(Counter + Gauge)
@Bean
public MeterBinder couponMetrics(CouponService couponService) {
return registry -> {
Counter issued = registry.counter("coupons.issued");
Counter redeemed = registry.counter("coupons.redeemed");
// 核销率 = redeemed / issued,使用 Gauge 实时计算
Gauge.builder("coupons.redeem.rate", () -> {
double issuedCount = issued.count();
return issuedCount == 0 ? 0 : redeemed.count() / issuedCount;
}).register(registry);
};
}
场景 3:消息队列积压监控(Gauge)
@Bean
public MeterBinder queueMetrics(MessageQueueClient client) {
return registry -> {
FunctionGauge.builder("queue.backlog.size", client,
c -> c.getUnprocessedCount())
.tag("queue", "order-events")
.description("消息队列积压数量")
.register(registry);
};
}
场景 4:用户活跃度(Counter)
@RestController
public class UserController {
private final Counter activeUserCounter;
public UserController(MeterRegistry registry) {
this.activeUserCounter = registry.counter("users.active", "action", "login");
}
@PostMapping("/login")
public ResponseEntity<?> login() {
activeUserCounter.increment();
// ...
}
}
六、最佳实践
✅ 正确做法
- 使用
MeterBinder封装业务指标 - 优先使用
FunctionGauge而非手动Gauge - 添加有意义的标签(tags)
.tag("status", "pending") .tag("region", "cn-east") - 设置指标描述和单位
.description("待处理订单数量") .baseUnit("orders") - 避免高基数标签(如用户ID、订单号)
- 启用直方图用于 Timer
management.metrics.distribution.percentile-histogram.orders.create: true
❌ 错误做法
// 错误 1:每次调用都创建新 Counter
registry.counter("orders").increment();
// 错误 2:使用高基数标签
registry.counter("orders", "userId", userId).increment();
// 错误 3:在循环中频繁创建 Meter
for (Order order : orders) {
registry.counter("item.processed").increment(); // 应复用 Counter
}
七、与监控系统集成(Prometheus + Grafana)
1. Prometheus 配置
scrape_configs:
- job_name: 'order-service'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['order-service:8080']
2. Grafana 查询示例
# 订单创建速率
rate(orders_created_total[5m])
# 支付平均耗时
histogram_quantile(0.95, sum(rate(payment_duration_seconds_bucket[5m])) by (le))
# 待处理订单数
orders_pending
八、总结
| 需求 | 推荐方式 |
|---|---|
| 简单计数 | Counter + MeterRegistry |
| 状态监控 | FunctionGauge |
| 方法耗时 | @Timed 注解 |
| 复杂业务指标 | MeterBinder |
| 高频调用 | 启用 percentile-histogram |
自定义业务指标设计原则:
- 业务驱动:指标要有明确的业务含义
- 可操作:能指导告警、优化、决策
- 低开销:避免影响主流程性能
- 可聚合:支持按标签切片分析
- 可维护:封装良好,易于扩展
通过合理设计和实现自定义业务指标,你可以构建一个真正反映业务健康度的监控体系,实现从“系统可观测”到“业务可观测”的跨越。
951

被折叠的 条评论
为什么被折叠?



