SpringBoot Reactive中Micrometer @Timed注解无法采集方法指标的问题
在SpringBoot响应式流水线中用Micrometer采集方法指标的正确姿势
问题根源
你遇到的问题本质有两个核心原因:
- Spring AOP管不到私有方法:
TimedAspect是基于Spring AOP实现的,而AOP代理只能拦截公共方法的外部调用。你的transformToComplexStringMethodIssueWithTimed是私有方法,属于类内部直接调用,完全绕过了AOP代理,所以@Timed注解根本不会被触发。 - 响应式场景的适配盲区:即使是公共方法,直接用@Timed注解在响应式流水线的同步方法上,虽然能勉强采集,但不符合响应式非阻塞的设计原则,还可能出现上下文丢失的问题。
正确实现方案
下面给出三种可行的方案,按需选择:
方案1:将私有方法改为公共方法+触发AOP代理
把私有方法改成公共方法,同时确保调用时走代理对象(避免内部直接调用绕过AOP):
@Service public class MyClass { // 注入自身的代理对象,用于内部调用触发AOP @Autowired private MyClass selfProxy; public Flux<String> mainReactivePipelineETL() { return Flux.interval(Duration.ofSeconds(1)) .map(i -> selfProxy.transformToComplexStringMethodIssueWithTimed(i)); } @Timed("search_for_me") public String transformToComplexStringMethodIssueWithTimed(Long i) { // 复杂转换逻辑 return String.valueOf(i); } }
如果不想注入代理对象,也可以用AopContext.currentProxy(),但需要在启动类添加@EnableAspectJAutoProxy(exposeProxy = true)开启代理暴露。
方案2:编程式调用Micrometer Timer(最灵活,适合私有方法)
不需要依赖AOP,直接在方法内部手动计时,不受方法访问权限限制:
@Service public class MyClass { private final Timer searchTimer; // 从MeterRegistry创建并注册Timer public MyClass(MeterRegistry registry) { this.searchTimer = Timer.builder("search_for_me") .description("复杂字符串转换耗时统计") .register(registry); } public Flux<String> mainReactivePipelineETL() { return Flux.interval(Duration.ofSeconds(1)) .map(i -> transformToComplexStringMethodIssueWithTimed(i)); } private String transformToComplexStringMethodIssueWithTimed(Long i) { // 用Timer记录方法执行耗时 return searchTimer.record(() -> { // 模拟复杂转换逻辑 try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return String.valueOf(i); }); } }
方案3:响应式原生Metrics操作符(推荐全响应式链路)
如果你的转换逻辑可以改成响应式返回(Mono),用Micrometer提供的响应式操作符,完全适配非阻塞流水线:
@Service public class MyClass { public Flux<String> mainReactivePipelineETL() { return Flux.interval(Duration.ofSeconds(1)) .flatMap(i -> transformToComplexStringReactive(i) .name("search_for_me") .tag("type", "reactive-transform") .metrics()); } private Mono<String> transformToComplexStringReactive(Long i) { return Mono.fromCallable(() -> { // 复杂转换逻辑 try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return String.valueOf(i); }); } }
验证指标
启动应用后,访问/actuator/prometheus端点,搜索search_for_me即可看到对应的指标:
search_for_me_total:方法调用总次数search_for_me_seconds:耗时分布(包含分位数、最大值等统计维度)
内容的提问来源于stack exchange,提问作者PatPanda
相关产品推荐
相关产品推荐

