spring boot 源码解析51-MetricExporters详解

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

前言

我们在上篇文章中讲解了Exporter的实现,Exporter最终是需要定时器来调度的,那么本文就来分析一下其实现.同时,定时器是需要配置的,因此抽象出TriggerProperties.其类图如下:

20191017100439\_1.png

解析

TriggerProperties

TriggerProperties–> 定时器配置的抽象基类.其声明了如下字段:

    // 2次export之间的间隔时间
    private Long delayMillis;

    // 是否该metric 可以导出
    private boolean enabled = true;

    // 是否关闭任何可优化的但是未导出的指标值
    private Boolean sendLatest;

    /** * List of patterns for metric names to include. */
    private String[] includes;

    /** * List of patterns for metric names to exclude. Applied after the includes. */
    private String[] excludes;

SpecificTriggerProperties

SpecificTriggerProperties–> 继承自TriggerProperties.其声明了如下字段:

    // 指定满足该名字(或者正则)的bean 的id
    private String[] names;

MetricExportProperties

MetricExportProperties 继承自TriggerProperties.

  1. 声明了如下字段:

        // key--> MetricWriter bean的id,value --> SpecificTriggerProperties
        private Map triggers = new LinkedHashMap();
    
        private Aggregate aggregate = new Aggregate();
    
        private Redis redis = new Redis();
    
        private Statsd statsd = new Statsd();

    其中:

    1. Aggregate–> Aggregate的配置类,声明了如下字段:

          // repository的全局前缀.应该在jvm中是全局唯一的,但是大多数这个是很有用的,如果其形如"a.b",a-->逻辑 b-->物理进程.
          private String prefix = "";
      
          // 指明该aggregator 该对repository 中的什么类型的key 进行操作
          private String keyPattern = "";
    2. Redis–> redis的配置类,声明了如下字段:

          // redis repository 的前缀,应该全局唯一
          private String prefix = "spring.metrics";
      
          // redis repository export 的key,全局唯一
          private String key = "keys.spring.metrics";
    3. Statsd –> Statsd的配置类,声明了如下字段:

          // statsd的主机名
          private String host;
      
          // statsd的端口号
          private int port = 8125;
      
          // 向statsd暴露metrics时添加的前缀s
          private String prefix;
  2. 由于该类中的setUpDefaults被@PostConstruct注解,因此该方法会在MetricExportProperties初始化完毕之后调用.代码如下:

        @PostConstruct
        public void setUpDefaults() {
            TriggerProperties defaults = this;
            // 1. 遍历triggers,
            for (Entry entry : this.triggers.entrySet()) {
                String key = entry.getKey();
                SpecificTriggerProperties value = entry.getValue();
                // 1.1 如果SpecificTriggerProperties 没有指定名字,则将其赋值为MetricWriter bean的id
                if (value.getNames() == null || value.getNames().length == 0) {
                    value.setNames(new String[] { key });
                }
            }
            if (defaults.isSendLatest() == null) {
                defaults.setSendLatest(true);
            }
            if (defaults.getDelayMillis() == null) {
                defaults.setDelayMillis(5000);
            }
            for (TriggerProperties value : this.triggers.values()) {
                if (value.isSendLatest() == null) {
                    value.setSendLatest(defaults.isSendLatest());
                }
                if (value.getDelayMillis() == null) {
                    value.setDelayMillis(defaults.getDelayMillis());
                }
            }
        }
  3. 由于该类声明了@ConfigurationProperties(prefix = “spring.metrics.export”)注解,因此可以通过如下进行配置:

        spring.metrics.export.aggregate.key-pattern= # Pattern that tells the aggregator what to do with the keys from the source repository.
        spring.metrics.export.aggregate.prefix= # Prefix for global repository if active.
        spring.metrics.export.delay-millis=5000 # Delay in milliseconds between export ticks. Metrics are exported to external sources on a schedule with this delay.
        spring.metrics.export.enabled=true # Flag to enable metric export (assuming a MetricWriter is available).
        spring.metrics.export.excludes= # List of patterns for metric names to exclude. Applied after the includes.
        spring.metrics.export.includes= # List of patterns for metric names to include.
        spring.metrics.export.redis.key=keys.spring.metrics # Key for redis repository export (if active).
        spring.metrics.export.redis.prefix=spring.metrics # Prefix for redis repository if active.
        spring.metrics.export.send-latest= # Flag to switch off any available optimizations based on not exporting unchanged metric values.
        spring.metrics.export.statsd.host= # Host of a statsd server to receive exported metrics.
        spring.metrics.export.statsd.port=8125 # Port of a statsd server to receive exported metrics.
        spring.metrics.export.statsd.prefix= # Prefix for statsd exported metrics.
        spring.metrics.export.triggers.*= # Specific trigger properties per MetricWriter bean name.

MetricExporters

MetricExporters–> 实现了SchedulingConfigurer, Closeable接口.

  1. 字段,构造器如下:

        private MetricReader reader;
    
        private Map writers = new HashMap();
    
        // 配置文件
        private final MetricExportProperties properties;
    
        // key--> Exporter bean 的id, value --> Exporter
        private final Map exporters = new HashMap();
    
        // MetricCopyExporter 所对应的 Exporter bean 的id(closeables中持有的是writers的key)
        private final Set closeables = new HashSet();
    
        public MetricExporters(MetricExportProperties properties) {
            this.properties = properties;
        }
  2. 方法实现如下:

    1. configureTasks–> SchedulingConfigurer 中声明的接口方法.代码如下:

          public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
              // 1. 遍历exporters
              for (Entry entry : this.exporters.entrySet()) {
                  String name = entry.getKey();
                  Exporter exporter = entry.getValue();
                  // 1.1 获得属于该Exporter的TriggerProperties
                  TriggerProperties trigger = this.properties.findTrigger(name);
                  if (trigger != null) {
                      // 1.2 如果存在TriggerProperties,则实例化ExportRunner
                      ExportRunner runner = new ExportRunner(exporter);
                      // 实例化IntervalTask,其内部会执行ExportRunner的run方法.在实例化后延迟DelayMillis后执行,之后每隔DelayMillis后执行
                      IntervalTask task = new IntervalTask(runner, trigger.getDelayMillis(),
                              trigger.getDelayMillis());
                      // 1.3 添加到ScheduledTaskRegistrar中
                      taskRegistrar.addFixedDelayTask(task);
                  }
              }
              // 2. 遍历writers
              for (Entry entry : this.writers.entrySet()) {
                  String name = entry.getKey();
                  GaugeWriter writer = entry.getValue();
                  // 2.1 获得属于该Exporter的TriggerProperties
                  TriggerProperties trigger = this.properties.findTrigger(name);
                  if (trigger != null) {
                      // 2.2 根据GaugeWriter 创建MetricCopyExporter
                      MetricCopyExporter exporter = getExporter(writer, trigger);
                      // 2.3 添加到exporters,closeables 中
                      this.exporters.put(name, exporter);
                      this.closeables.add(name);
                      // 2.4 实例化ExportRunner,IntervalTask,添加到ScheduledTaskRegistrar中 
                      ExportRunner runner = new ExportRunner(exporter);
                      IntervalTask task = new IntervalTask(runner, trigger.getDelayMillis(),
                              trigger.getDelayMillis());
                      taskRegistrar.addFixedDelayTask(task);
                  }
              }
          }
      1. 遍历exporters

        1. 获得属于该Exporter的TriggerProperties
        2. 如果存在TriggerProperties,则实例化ExportRunner
        3. 实例化IntervalTask,其内部会执行ExportRunner的run方法.在实例化后延迟DelayMillis后执行,之后每隔DelayMillis后执行
        4. 添加到ScheduledTaskRegistrar中
      2. 遍历writers

        1. 获得属于该Exporter的TriggerProperties
        2. 根据GaugeWriter 创建MetricCopyExporter.代码如下:

              private MetricCopyExporter getExporter(GaugeWriter writer,
                      TriggerProperties trigger) {
                  MetricCopyExporter exporter = new MetricCopyExporter(this.reader, writer);
                  exporter.setIncludes(trigger.getIncludes());
                  exporter.setExcludes(trigger.getExcludes());
                  exporter.setSendLatest(trigger.isSendLatest());
                  return exporter;
              }
        3. 添加到exporters,closeables 中
        4. 实例化ExportRunner,IntervalTask,添加到ScheduledTaskRegistrar中

      ExportRunner实现了Runnable接口,代码如下:

          private static class ExportRunner implements Runnable {
      
              private final Exporter exporter;
      
              ExportRunner(Exporter exporter) {
                  this.exporter = exporter;
              }
      
              @Override
              public void run() {
                  this.exporter.export();
              }
      
          }
    2. close,代码如下:

          public void close() throws IOException {
              for (String name : this.closeables) {
                  Exporter exporter = this.exporters.get(name);
                  if (exporter instanceof Closeable) {
                      ((Closeable) exporter).close();
                  }
              }
          }
      1. 遍历其持有的closeables(closeables中持有的是writers的key)
      2. 依次获得对应的Exporter,如果其实现了Closeable接口,则调用其close方法(此时实际获得的是MetricCopyExporter,其实现了Closeable)

MetricExporters 自动装配

MetricExporters的自动配置类为MetricExportAutoConfiguration.

  1. 该类声明了如下注解:

        @Configuration
        @EnableScheduling
        @ConditionalOnProperty(value = "spring.metrics.export.enabled", matchIfMissing = true)
        @EnableConfigurationProperties
    • @Configuration –> 配置类
    • @EnableScheduling –> 引入了SchedulingConfiguration,在该类中注册了ScheduledAnnotationBeanPostProcessor,开启了对@Scheduled注解的处理
    • @ConditionalOnProperty(value = “spring.metrics.export.enabled”, matchIfMissing = true) –> 当配置有spring.metrics.export.enabled=true时生效,如果没有配置的话,则默认生效
    • @EnableConfigurationProperties –> 引入了EnableConfigurationPropertiesImportSelector,最终执行其selectImports,由于此时我们没有配置value值,因此会返回ConfigurationPropertiesBindingPostProcessorRegistrar.该类会注册ConfigurationBeanFactoryMetaData(关于这个,我们之前已经讲过很多次了)
  2. MetricExportAutoConfiguration中有2个配置类:

    1. MetricExportPropertiesConfiguration,代码如下:

          @Configuration
          protected static class MetricExportPropertiesConfiguration {
      
              // 默认为application.随机值 如果配置了spring.application.name 则为spring.application.name.随机值
              @Value("${spring.application.name:application}.${random.value:0000}")
              private String prefix = "";
      
              private String aggregateKeyPattern = "k.d";
      
              @Bean(name = "spring.metrics.export-org.springframework.boot.actuate.metrics.export.MetricExportProperties")
              @ConditionalOnMissingBean
              public MetricExportProperties metricExportProperties() {
                  // 1. 实例化MetricExportProperties
                  MetricExportProperties export = new MetricExportProperties();
                  // 2. 设置Redis的前缀
                  export.getRedis().setPrefix("spring.metrics"
                          + (this.prefix.length() > 0 ? "." : "") + this.prefix);
                  // 3. 设置Aggregate的前缀
                  export.getAggregate().setPrefix(this.prefix);
                  // 4. 设置Aggregate的KeyPattern为k.d
                  export.getAggregate().setKeyPattern(this.aggregateKeyPattern);
                  return export;
              }
      
          }

      当BeanFactory中不存在MetricExportProperties类型的bean时,会执行metricExportProperties方法,进行注册–>id为spring.metrics.export-org.springframework.boot.actuate.metrics.export.MetricExportProperties

    2. StatsdConfiguration,代码如下:

          @Configuration
          static class StatsdConfiguration {
      
              @Bean
              @ExportMetricWriter
              @ConditionalOnMissingBean
              @ConditionalOnProperty(prefix = "spring.metrics.export.statsd", name = "host")
              public StatsdMetricWriter statsdMetricWriter(MetricExportProperties properties) {
                  MetricExportProperties.Statsd statsdProperties = properties.getStatsd();
                  return new StatsdMetricWriter(statsdProperties.getPrefix(),
                          statsdProperties.getHost(), statsdProperties.getPort());
              }
      
          }

      当满足如下条件进行注册:

      1. @ConditionalOnMissingBean –> BeanFactory中不存在StatsdMetricWriter类型的bean时生效
      2. @ConditionalOnProperty(prefix = “spring.metrics.export.statsd”, name = “host”) –> 配置有spring.metrics.export.statsd.host 时生效

      个人觉得,此处的代码有问题,当我们配置有spring.metrics.export.statsd.host,确没有在项目中加入java-statsd-client的依赖,此处就会抛出java.lang.ClassNotFoundException: com.timgroup.statsd.StatsDClientErrorHandler

      解决方案:在该方法上加上@ConditionalOnClass(name=”com.timgroup.statsd.NonBlockingStatsDClient”)

      该bug在spring-boot 2.0.0.M5中fix.在2.0.0.M4 中不支持StatsdMetricWriter.

  3. 该类的字段,构造器如下:

    
        // 默认情况,没有自动装配
        private final MetricsEndpointMetricReader endpointReader;
    
        // 此处获得的是被@ExportMetricReader注解的MetricReader
        private final List readers;
    
        // 此处获得的是被@ExportMetricWriter注解的GaugeWriter,key--> GaugeWriter的id,value --> GaugeWriter
        private final Map writers;
    
        // key --> Exporter bean 的id,value --> Exporter
        private final Map exporters;
    
        public MetricExportAutoConfiguration(MetricExportProperties properties,
                ObjectProvider endpointReader,
                @ExportMetricReader ObjectProvider> readers,
                @ExportMetricWriter ObjectProvider> writers,
                ObjectProvider> exporters) {
            this.endpointReader = endpointReader.getIfAvailable();
            this.readers = readers.getIfAvailable();
            this.writers = writers.getIfAvailable();
            this.exporters = exporters.getIfAvailable();
        }

    其字段的说明如下:

    • MetricsEndpointMetricReader endpointReader –> 默认情况,没有自动装配
    • List readers –> 此处获得的是被@ExportMetricReader注解的MetricReader

      1. 在jdk1.8及以上的环境中并且没有加入metrics-core依赖时,获得的是BufferMetricReader
      2. 在jdk1.8以前的环境中并且BaanFactory中不存在id为actuatorMetricRepository的bean时,LegacyMetricRepositoryConfiguration会生效.因此此时获得的是InMemoryMetricRepository,InMemoryMultiMetricRepository
    • Map writers–> 此处获得的是被@ExportMetricWriter注解的GaugeWriter,key–> GaugeWriter的id,value –> GaugeWriter:

      在加入spring-boot-starter-integration,并且注册了id为metricsChannel,类型为metricsChannel的bean之后,MetricsChannelAutoConfiguration配置类就会生效,因此会获得MessageChannelMetricWriter

    • exporters –> key –> Exporter bean 的id,value –> Exporter.默认情况下,此时获得的是空集合
  4. 在该类中,声明了1个@Bean方法,如下:

        @Bean
        @ConditionalOnMissingBean(name = "metricWritersMetricExporter")
        public SchedulingConfigurer metricWritersMetricExporter(
                MetricExportProperties properties) {
            Map writers = new HashMap();
            // 1. 如果endpointReader 没有配置并且readers 不为空,则实例化CompositeMetricReader
            MetricReader reader = this.endpointReader;
            if (reader == null && !CollectionUtils.isEmpty(this.readers)) {
                reader = new CompositeMetricReader(
                        this.readers.toArray(new MetricReader[this.readers.size()]));
            }
            // 2. 如果reader 等于null并且exporters 为空,则返回NoOpSchedulingConfigurer
            if (reader == null && CollectionUtils.isEmpty(this.exporters)) {
                return new NoOpSchedulingConfigurer();
            }
            // 3. 实例化MetricExporters
            MetricExporters exporters = new MetricExporters(properties);
            // 3.1 如果reader存在的话
            if (reader != null) {
                // 如果GaugeWriter不回空,则加入到writers中
                if (!CollectionUtils.isEmpty(this.writers)) {
                    writers.putAll(this.writers);
                }
                // 3.2 赋值到MetricExporters中
                exporters.setReader(reader);
                exporters.setWriters(writers);
            }
            // 3.3 设置exporters
            exporters.setExporters(this.exporters == null
                    ? Collections.emptyMap() : this.exporters);
            return exporters;
        }

    该方法的逻辑如下:

    1. 如果endpointReader 没有配置并且readers 不为空,则实例化CompositeMetricReader. 默认情况下会执行此步.
    2. 如果reader 等于null并且exporters 为空,则返回NoOpSchedulingConfigurer,此时会在加入metrics-core依赖时生效.默认不执行此步骤
    3. 实例化MetricExporters

      1. 如果reader存在的话
      2. 如果GaugeWriter不回空,则加入到writers中,此步骤默认不执行,需要加入spring-boot-starter-integration依赖
      3. 赋值到MetricExporters中
    4. 设置exporters. 默认exporters等于null,则此时传入的是空集合

    因此:

    1. 默认情况下,MetricExporters中不会配置定时任务
    2. 加入spring-boot-starter-integration,并且注册了id为metricsChannel之后,会注册1个定时任务,此时会将BufferMetricReader中的信息写到名为metricsChannel的Channel中.该步骤要想成功,需要如下几步:

      1. 在pom文件加入如下依赖:

            
                
                org.springframework.boot
                spring-boot-starter-integration
            
            
                org.springframework.integration
                spring-integration-stream
                4.3.12.RELEASE
            
      2. 在配置类加入如下配置:

            @Bean
            @ExportMetricWriter
            public MessageChannel metricsChannel() {
                return new DirectChannel();
            }
        
            @Bean
            public IntegrationFlow test() {
                return IntegrationFlows.from("metricsChannel")
                        .handle(CharacterStreamWritingMessageHandler.stdout())
                        .get();
            }
      3. 在controller中加入如下代码:

            @Autowired
            private CounterService counterService;
        
            @RequestMapping("/test-counter")
            public String testCounter() {
        
                counterService.increment("test-counter.count");
        
                return "操作成功";
            }
      4. 此时访问http://127.0.0.1:8080/test-counter后,会发现控制台打印如下日志:

            Metric [name=gauge.response.test-counter, value=32.0, timestamp=Wed Jan 31 17:36:36 CST 2018]Metric [name=gauge.response.star-star.favicon.ico, value=14.0, timestamp=Wed Jan 31 17:36:36 CST 2018]
            Metric [name=counter.status.200.star-star.favicon.ico, value=1, timestamp=Wed Jan 31 17:36:36 CST 2018]Metric [name=counter.test-counter.count, value=1, timestamp=Wed Jan 31 17:36:36 CST 2018]
            Metric [name=counter.status.200.test-counter, value=1, timestamp=Wed Jan 31 17:36:36 CST 2018]

        关于这部分的内容,请看spring boot 源码解析42-MessageChannelMetricWriter详解


来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring boot 源码解析51-MetricExporters详解

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏