4-指标转换与导出

Dubbo 指标模块源码分析-指标转换与导出

四、指标转换与导出

本章主要梳理指标收集完成后,向外部收集器导出的流程。

通过之前的分析,我们知道不同类型指标的收集分别由各个 Collector 实现进行。它们底层的 MetricsCollector 接口定义了指标导出的操作。

@SPI
public interface MetricsCollector<E extends TimeCounterEvent> extends MetricsLifeListener<E> {

    default boolean isCollectEnabled() {
        return false;
    }

    /**
     * Collect metrics as {@link MetricSample}
     *
     * @return List of MetricSample
     */
    List<MetricSample> collect();
}

而指标报告器 (MetricsReporter) 的实现会定时调用Collector 的 collect 方法,更新并导出指标数据。

public interface MetricsReporter {
    //初始化
    void init();

    //刷新统计数据,定时调用collect()
    void refreshData();

    //获取指标数据
    String getResponse();
    
    //获取带指标名的指标样本(单个指标)
    default String getResponseWithName(String metricsName) {   return null; }
}

指标报告器有两个实现:DefaultMetricsReporter 和 PrometheusMetricsReporter,它们都实现自 AbstractMetricsRepoter,并使用它的指标刷新逻辑 (refreshData方法)。

AbstractMetricsRepoter 初始化时会获取并保存所有 Collector 的实现,每次刷新数据,调用refreshData方法时都会遍历这些收集器,更新指标数据。

//AbstractMetricsRepoter

    private void initCollectors() {
     ...
        List<MetricsCollector> otherCollectors = beanFactory.getBeansOfType(MetricsCollector.class);
        collectors.addAll(otherCollectors);
    }

    public void refreshData() {
        collectors.forEach(collector -> {
            List<MetricSample> samples = collector.collect();
            for (MetricSample sample : samples) {
                ...
                 //将Dubbo的度量类型适配为micrometer的度量类型,并将其添加到CompositeMeterRegistry中,借此实现多监控系统的支持。
            }
     });        

DefaultMetricsReporter 和 PrometheusMetricsReporter 各自实现了自己的指标采样逻辑 (getResponse方法)。

PrometheusMetricsReporter

它通过 PrometheusMeterRegistry 获取普罗米修斯支持格式的样本数据:

//PrometheusMetricsReporter
    public String getResponse() {
        return prometheusRegistry.scrape();
    }

在初始化时,它会开启一个定时任务,定时向普罗米修斯服务端推送采样数据:

//PrometheusMetricsReporter

   SimpleMeterRegistry meterRegistry = new SimpleMeterRegistry();

   @Override
    public void doInit() {
        addMeterRegistry(prometheusRegistry);
        schedulePushJob();
    }

    private void schedulePushJob() {
         //这里的URL是DefaultMetricsReporter中定义的指标报告URL,提供了指标服务的具体地址
        boolean pushEnabled = url.getParameter(PROMETHEUS_PUSHGATEWAY_ENABLED_KEY, false);
        if (pushEnabled) {
       ...
            pushJobExecutor.scheduleWithFixedDelay(() -> push(pushGateway, job), pushInterval, pushInterval, TimeUnit.SECONDS);
        }
    }

    protected void push(PushGateway pushGateway, String job) {
        ...
            refreshData();
            //将本次采样数据添加到pushGateway,等待下次抓取
            pushGateway.pushAdd(prometheusRegistry.getPrometheusRegistry(), job);
        ...
    }

PushGateway 和 PrometheusRegistry 均为 micrometer 提供的 API。

PrometheusRegistry 将度量数据转换为普罗米修斯支持的格式,而 PushGateway 存储样本,暴露一个HTTP端点供普罗米修斯服务端抓取。

PushGateway 本身只是一个度量数据的缓存区,普罗米修斯服务端每从其中抓取一次数据,其内部的样本就会被清除。

DefaultMetricsReporter

指标报告器的默认实现,提供了按指标名称导出特定指标的方法。

//DefaultMetricsReporter 

    @Override
    public String getResponse() {
        return null;
    }

    @Override
    public String getResponseWithName(String metricsName) {
        ...
        meterRegistry.getMeters().stream().filter(meter -> {
             //根据名称过滤样本
        });
        metricsValue.forEach((key, value) -> {
             //按格式拼装结果
        });
        return sb.toString();
    }

    @Override
    protected void doInit() {
        addMeterRegistry(meterRegistry);
    }

    @Override
    protected void doDestroy() {}
}

该实现使用的 SimpleMeterRegistry 本身只会存储指标数据的功能,而不像 PrometheusMeterRegistry 那样提供发布数据的方法。

因此该指标报告器不会主动向外部发布数据,而是被动的通过 getResponseWithName 提供数据。

而且,该报告器在任何情况下都会被初始化:

//DefaultApplicationDeployer#initMetricsReporter
//If the protocol is not the default protocol, the default protocol is also initialized.
        if (!PROTOCOL_DEFAULT.equals(metricsConfig.getProtocol())) {
            DefaultMetricsReporterFactory defaultMetricsReporterFactory = new DefaultMetricsReporterFactory(applicationModel);
            MetricsReporter defaultMetricsReporter = defaultMetricsReporterFactory.createMetricsReporter(metricsConfig.toUrl());
            defaultMetricsReporter.init();
            applicationModel.getBeanFactory().registerBean(defaultMetricsReporter);
        }
...

实际上,该类主要在用户使用Qos命令查询时提供指定指标数据,而非提供给某个特定的外部指标中心。

//Qos命令
public class DefaultMetricsReporterCmd implements BaseCommand {
...
private String getResponseByApplication(ApplicationModel applicationModel, String metricsName) {
        String response = "DefaultMetricsReporter not init";
        MetricsReporter metricsReporter = applicationModel.getBeanFactory().getBean(DefaultMetricsReporter.class);
        if (metricsReporter != null) {
            metricsReporter.refreshData();
            //获取指定名称指标的数据
            response = metricsReporter.getResponseWithName(metricsName);
        }
        return response;
    }
}
...
}

*需要注意的是,PrometheusMetricsReporter 也支持使用 Qos 命令查询内部指标数据,同样有对应的 PrometheusMetricsReporterCmd 实现,它们的工作原理相似。

以上就是指标样本从收集完成到最终导出到外部指标中心的大致流程。