You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot如何将Prometheus指标暴露到默认/actuator/prometheus端点

问题说明

在Spring Boot应用中基于Micrometer搭配Prometheus registry暴露自定义Gauge指标时,参考非Spring环境的Micrometer示例手动配置PrometheusRegistry后,应用额外启动了监听8081端口的独立HTTP服务器暴露Prometheus指标,目标是取消该独立服务,将所有指标统一暴露到Spring Boot默认的/actuator/prometheus路径下。

原有错误实现代码

PrometheusRegistry.java

package com.xyz.abc.prometheus;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.time.Duration;

import com.sun.net.httpserver.HttpServer;

import io.micrometer.core.lang.Nullable;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;

public class PrometheusRegistry {

    public static PrometheusMeterRegistry prometheus() {
        PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(new PrometheusConfig() {
            @Override
            public Duration step() {
                return Duration.ofSeconds(10);
            }

            @Override
            @Nullable
            public String get(String k) {
                return null;
            }
        });

        try {
            HttpServer server = HttpServer.create(new InetSocketAddress(8081), 0);
            server.createContext("/sample-data/prometheus", httpExchange -> {
                String response = prometheusRegistry.scrape();
                httpExchange.sendResponseHeaders(200, response.length());
                OutputStream os = httpExchange.getResponseBody();
                os.write(response.getBytes());
                os.close();
            });

            new Thread(server::start).run();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return prometheusRegistry;
    }
    
}

MicrometerConfig.java

package com.xyz.abc.prometheus;

import io.micrometer.core.instrument.MeterRegistry;


public class MicrometerConfig {

    public static MeterRegistry carMonitoringSystem() {
        return PrometheusRegistry.prometheus();
    }
}

接口内注册指标的错误写法

@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping(value = "/sampleApi", method= RequestMethod.GET)
@ResponseBody
// @Timed生成的指标可正常出现在/actuator/prometheus路径
@Timed(value = "car.healthcheck", description = "Time taken to return healthcheck")
public ResponseEntity healthCheck(){
    MeterRegistry registry = MicrometerConfig.carMonitoringSystem();
    AtomicLong n = new AtomicLong();
    // 以下Gauge指标仅出现在8081端口的/sample-data/prometheus路径,不会进入默认Actuator端点
    registry.gauge("car.gauge.one", Tags.of("k", "v"), n);
    registry.gauge("car.gauge.two", Tags.of("k", "v1"), n, n2 -> n2.get() - 1);
    registry.gauge("car.help.gauge", 89);
    
    // 以下Gauge.builder定义的指标在所有端点都不显示
    Gauge.builder("car.gauge.test", cpu)
         .description("car.device.cpu")
         .tags("customer", "demo")
         .register(registry);
    return new ResponseEntity("Car is working fine.", HttpStatus.OK);
}
问题根因
  • 手动实例化了独立的PrometheusMeterRegistry,还自行启动了8081端口的HTTP服务。你参考的是无Spring容器的纯Java示例,在Spring Boot环境下,只要引入micrometer-registry-prometheus依赖,框架会自动创建并管理全局唯一的PrometheusMeterRegistry实例,/actuator/prometheus端点只会抓取这个Spring管理的实例中的指标。你自己new出来的Registry和Spring管理的Registry完全隔离,所以往自建Registry里注册的指标不会出现在默认端点。@Timed注解的指标能正常展示,就是因为它自动注册到了Spring管理的Registry中。
  • 指标注册时机错误,且被观测对象是方法局部变量。你把Gauge注册逻辑写在了接口请求方法里,每次请求都会重复执行注册逻辑;同时作为Gauge观测值载体的AtomicLong n是方法内局部变量,请求结束后就会被GC回收。Micrometer对Gauge的被观测对象默认只持弱引用,一旦对象被回收,对应的Gauge指标会直接消失。
  • Gauge用法错误:直接传入固定常量89注册Gauge是无效写法,Gauge的设计目的是观测动态变化的值,固定字面量不会生成可被采集的有效指标。
修复方案
  1. 直接删除你自行编写的PrometheusRegistry.java和MicrometerConfig.java两个类,不需要手动创建Registry实例、不需要手动启动HTTP服务暴露端点,Spring Boot自动配置会完成这部分工作。
  2. 确保项目中引入了正确的依赖,以Maven为例:
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  1. 编写专门的指标注册类,在Bean初始化阶段一次性完成所有Gauge注册,被观测的对象必须是持有强引用的类成员变量,避免被GC回收:
package com.xyz.abc.prometheus;

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

import javax.annotation.PostConstruct;
import java.util.concurrent.atomic.AtomicLong;

@Component
public class CarMetrics {
    private final MeterRegistry meterRegistry;
    // 指标值载体定义为类成员变量,持有强引用
    private final AtomicLong gaugeValueHolder = new AtomicLong(0);
    // 被观测的CPU业务对象同样定义为成员变量
    private final CpuStats cpu = new CpuStats();

    // 直接注入Spring自动配置的全局MeterRegistry
    public CarMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    // Bean初始化完成后执行一次指标注册,不要重复注册
    @PostConstruct
    public void initMetrics() {
        Gauge.builder("car.gauge.one", gaugeValueHolder, AtomicLong::get)
                .tags("k", "v")
                .register(meterRegistry);

        Gauge.builder("car.gauge.two", gaugeValueHolder, val -> val.get() - 1)
                .tags("k", "v1")
                .register(meterRegistry);

        Gauge.builder("car.gauge.test", cpu, CpuStats::getUsage)
                .description("car.device.cpu")
                .tags("customer", "demo")
                .register(meterRegistry);
    }

    // 对外提供更新指标值的方法,供业务逻辑调用
    public void updateGaugeValue(long value) {
        this.gaugeValueHolder.set(value);
    }

    // CPU指标统计内部类示例
    public static class CpuStats {
        public double getUsage() {
            // 替换为实际的CPU使用率采集逻辑
            return Runtime.getRuntime().availableProcessors();
        }
    }
}
  1. 修改接口逻辑,接口中仅做指标值更新,不要执行指标注册操作:
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping(value = "/sampleApi", method= RequestMethod.GET)
@ResponseBody
@Timed(value = "car.healthcheck", description = "Time taken to return healthcheck")
public ResponseEntity healthCheck(){
    // 注入CarMetrics Bean,更新指标值即可
    carMetrics.updateGaugeValue(ThreadLocalRandom.current().nextLong(100));
    return new ResponseEntity("Car is working fine.", HttpStatus.OK);
}
  1. 在application.yml中开启Prometheus端点暴露:
management:
  endpoints:
    web:
      exposure:
        include: prometheus,health
  endpoint:
    prometheus:
      enabled: true

配置完成后重启应用,8081端口的独立HTTP服务不会再启动,所有@Timed指标、自定义Gauge指标都会统一在/actuator/prometheus路径下对外暴露。

内容的提问来源于stack exchange,提问作者Harshit Gupta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 23:39:14