如何为Prometheus Java客户端默认JMX指标注入自定义标签区分微服务
Prometheus Java客户端给默认指标追加自定义服务标签的实现方案
方案一:客户端侧统一追加全局标签(推荐)
不需要修改默认导出的指标逻辑,通过包装CollectorRegistry的方式,给所有输出的指标自动追加固定标签,最终暴露的指标本身就会携带service标签。
1. 自定义可追加全局标签的Registry代理
这个代理类会在收集所有指标样本时,自动合并你指定的全局标签到每个样本中:
import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.exporter.MetricsServlet; import java.util.ArrayList; import java.util.List; import java.util.Map; public class GlobalLabelRegistry extends CollectorRegistry { private final CollectorRegistry delegate; private final List<String> globalLabelNames; private final List<String> globalLabelValues; /** * @param delegate 原始的指标Registry * @param globalLabels 需要全局追加的固定标签键值对,比如 {"service": "foo"} */ public GlobalLabelRegistry(CollectorRegistry delegate, Map<String, String> globalLabels) { this.delegate = delegate; this.globalLabelNames = new ArrayList<>(globalLabels.keySet()); this.globalLabelValues = new ArrayList<>(globalLabels.values()); } @Override public List<MetricFamilySamples> collect() { List<MetricFamilySamples> originalSamples = delegate.collect(); List<MetricFamilySamples> wrappedSamples = new ArrayList<>(); for (MetricFamilySamples mfs : originalSamples) { List<MetricFamilySamples.Sample> wrappedSampleList = new ArrayList<>(); for (MetricFamilySamples.Sample sample : mfs.samples) { // 合并原有标签和全局标签 List<String> allLabelNames = new ArrayList<>(sample.labelNames); allLabelNames.addAll(globalLabelNames); List<String> allLabelValues = new ArrayList<>(sample.labelValues); allLabelValues.addAll(globalLabelValues); wrappedSampleList.add(new MetricFamilySamples.Sample( sample.name, allLabelNames, allLabelValues, sample.value )); } wrappedSamples.add(new MetricFamilySamples(mfs.name, mfs.type, mfs.help, wrappedSampleList)); } return wrappedSamples; } // 其余registry方法直接委托给原始实现即可 @Override public void register(Collector collector) { delegate.register(collector); } @Override public void unregister(Collector collector) { delegate.unregister(collector); } }
2. 修改原有监控服务初始化逻辑
不需要改动原来的Undertow启动逻辑,只需要调整指标初始化和Servlet注入的部分:
// 新建独立的指标Registry,避免污染全局默认Registry CollectorRegistry baseRegistry = new CollectorRegistry(); // 把默认JVM、系统指标注册到这个独立Registry DefaultExports.initialize(baseRegistry); // 定义需要追加的全局标签,service的值可以从配置、启动参数中读取 Map<String, String> globalLabels = Map.of("service", "foo"); // 包装Registry,绑定全局标签 GlobalLabelRegistry wrappedRegistry = new GlobalLabelRegistry(baseRegistry, globalLabels); // 初始化监控Servlet,传入包装后的Registry DeploymentInfo monitoringServletInfo = Servlets.deployment() .setClassLoader(MonitoringServer.class.getClassLoader()) .setContextPath("/") .setDeploymentName("monitoring") // 核心改动:传入带全局标签的Registry实例 .addServlet(Servlets.servlet(new MetricsServlet(wrappedRegistry)).addMapping("/*")); // 后续Undertow的启动逻辑和原代码完全一致,无需修改 DeploymentManager monitoringDeployment = Servlets.defaultContainer().addDeployment(monitoringServletInfo); monitoringDeployment.deploy(); HttpHandler monitoringHandler = monitoringDeployment.start(); server = Undertow.builder() .addHttpListener(port, "0.0.0.0") .setHandler( Handlers.path().addExactPath(HEALTH_PATH, new HealthHandler()) .addExactPath(MONITORING_PATH, monitoringHandler)) .build(); server.start();
实现后暴露的指标效果完全符合预期:
jvm_buffer_pool_used_bytes{pool="direct",service="foo"} 116776.0
注意:全局追加的标签名不要和现有指标已有的标签名重复,否则会出现标签冲突。
方案二:Prometheus抓取侧统一加标签(无需修改Java代码)
如果Prometheus服务是统一运维的,不需要每个微服务单独改造代码,也可以在Prometheus的抓取配置中给每个任务统一加标签:
scrape_configs: # 微服务foo的抓取配置 - job_name: 'service-foo' static_configs: - targets: ['foo服务的IP:端口'] # 直接给该任务抓取的所有指标绑定service标签 labels: service: 'foo' # 微服务bar的抓取配置 - job_name: 'service-bar' static_configs: - targets: ['bar服务的IP:端口'] labels: service: 'bar'
这种方式的优势是所有标签逻辑统一在Prometheus侧维护,不需要业务侧改动代码;缺点是指标本身暴露时不带service标签,仅在Prometheus存储、Grafana查询时可识别。
内容的提问来源于stack exchange,提问作者Denis Voloshin
相关产品推荐
相关产品推荐

