如何在Spring Boot中正确注册Micrometer Counter适配所有MeterRegistry
场景复现
首次尝试:构造函数注入MeterRegistry注册Counter
在服务构造函数中注入MeterRegistry并直接注册Counter:
@Service public class MyService { private final Counter fooCounter; public MyService(MeterRegistry meterRegistry) { fooCounter = Counter.builder("foo.count") .baseUnit("nr of foos") .register(meterRegistry); } public void foo() { fooCounter.increment(); } }
此时/actuator/metrics/foo.count能正常显示指标,但/actuator/prometheus中看不到该指标(其他默认指标正常)。
第二次尝试:实现MeterBinder接口
让服务类实现MeterBinder接口,在bindTo方法中注册Counter:
@Service public class MyService implements MeterBinder { private Counter fooCounter; public MyService() {} public void foo() { fooCounter.increment(); } @Override public void bindTo(MeterRegistry meterRegistry) { fooCounter = Counter.builder("foo.count") .baseUnit("nr of foos") .register(meterRegistry); } }
此时/actuator/prometheus中能看到指标,但出现了两个Counter实例:调用foo()方法时只有一个实例被递增,/actuator/metrics/foo.count的数值始终不更新。
环境信息
- Spring Boot版本:2.7.15
- Micrometer版本:1.9.14
- 自定义了
PrometheusMeterRegistry作为Primary Bean:
@Configuration public class MetricsConfig { @Bean @Primary public MeterRegistry metricRegistry() { return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); } }
解决方案
核心原因
Spring Boot默认会维护一个CompositeMeterRegistry作为主注册器,它会包含所有创建的子MeterRegistry(比如用于/actuator/metrics的SimpleMeterRegistry、用于Prometheus导出的PrometheusMeterRegistry)。你自定义的PrometheusMeterRegistry被标记为@Primary,导致注入的是单个Prometheus实例而非Composite实例,指标无法同步到所有注册器;而实现MeterBinder时,若没有基于Composite注册器绑定,会为每个子注册器创建独立的Counter实例,导致数值不同步。
方案1:移除自定义MeterRegistry Bean(推荐)
引入micrometer-registry-prometheus依赖后,Spring Boot会自动创建完整的注册器体系:Composite主注册器+子注册器。此时直接注入MeterRegistry(实际是Composite实例),注册的Counter会自动同步到所有子注册器:
@Service public class MyService { private final Counter fooCounter; public MyService(MeterRegistry meterRegistry) { fooCounter = Counter.builder("foo.count") .baseUnit("nr of foos") .register(meterRegistry); } public void foo() { fooCounter.increment(); } }
修改后,/actuator/metrics/foo.count和/actuator/prometheus会同时正常展示且数值保持同步。
方案2:保留自定义配置,构建Composite注册器
如果需要自定义PrometheusMeterRegistry的配置,不要直接将其设为@Primary,而是手动构建CompositeMeterRegistry并包含所有需要的子注册器:
@Configuration public class MetricsConfig { @Bean public PrometheusMeterRegistry prometheusMeterRegistry() { // 这里可以添加自定义配置,比如设置命名规则等 return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); } @Bean @Primary public CompositeMeterRegistry compositeMeterRegistry(PrometheusMeterRegistry prometheusMeterRegistry) { CompositeMeterRegistry composite = new CompositeMeterRegistry(); composite.add(prometheusMeterRegistry); composite.add(new SimpleMeterRegistry()); // 用于/actuator/metrics端点的注册器 return composite; } }
服务类依然用构造函数注入MeterRegistry,注册的Counter会同步到所有子注册器中。
方案3:正确实现MeterBinder接口
若坚持使用MeterBinder,需确保服务类被Spring正确识别为Bean,且基于Composite注册器绑定(避免为每个子注册器创建独立实例):
@Service public class MyService implements MeterBinder { private Counter fooCounter; @Override public void bindTo(MeterRegistry meterRegistry) { fooCounter = Counter.builder("foo.count") .baseUnit("nr of foos") .register(meterRegistry); } public void foo() { fooCounter.increment(); } }
此时Spring会自动将MeterBinder绑定到Composite主注册器,Counter只会被创建一次,且指标会同步到所有子注册器。
内容的提问来源于stack exchange,提问作者Joel

