spring boot 源码解析39-DropwizardMetricServices详解

前言

本文我们来介绍一下DropwizardMetricServices,该类实现了CounterService, GaugeService.

解析

DropwizardMetricServices–> 基于如下规则进行处理:

  1. 如果 increment 方法传入的名字是meter.开头的,则通过Meter来处理
  2. 其他的,则当做Counter 来处理
  3. 如果传入submit的names 所对应的参数值是histogram.开头的,则当做Histogram进行处理
  4. 如果传入submit的names 所对应的参数值是timer.开头的,则当做Timer进行处理
  5. 如果是其他,则当做Gauge 处理

由于其使用MetricRegistry实现功能的.这里有必要介绍一下

MetricRegistry介绍

MetricRegistry中有5种数据类型,spring boot中我们可以加入如下依赖:

<dependency>
    <groupId>io.dropwizard.metrics</groupId>
    <artifactId>metrics-core</artifactId>
</dependency>

方便做以下各种数据类型的测试.

  1. Gauges–> 个最简单的计量,一般用来统计瞬时状态的数据信息,比如系统中处于pending状态的job.测试代码如下:

    package metrics;
    import java.util.Queue;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.TimeUnit;
    import com.codahale.metrics.ConsoleReporter;
    import com.codahale.metrics.Gauge;
    import com.codahale.metrics.MetricRegistry;
    public class GaugesTests {
    
        /** * 实例化一个registry,最核心的一个模块,相当于一个应用程序的metrics系统的容器,维护一个Map */
        private static final MetricRegistry metrics = new MetricRegistry();
    
        private static Queue<String> queue = new LinkedBlockingDeque<String>();
    
        /** * 在控制台上打印输出 */
        private static ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();
    
        public static void main(String[] args) throws InterruptedException {
            reporter.start(3, TimeUnit.SECONDS);
    
            //实例化一个Gauge
            Gauge<Integer> gauge = new Gauge<Integer>() {
                @Override
                public Integer getValue() {
                    return queue.size();
                }
            };
    
            //注册到容器中
            metrics.register(MetricRegistry.name(GaugesTests.class, "pending-job", "size"), gauge);
    
            //模拟数据
            for (int i=0; i<20; i++){
                queue.add("a");
                Thread.sleep(1000);
            }
    
        }
    }

    打印结果如下:

    18-1-25 10:56:51 ===============================================================
    -- Gauges ----------------------------------------------------------------------
    metrics.GaugesTests.pending-job.size
                 value = 4
    18-1-25 10:56:54 ===============================================================
    -- Gauges ----------------------------------------------------------------------
    metrics.GaugesTests.pending-job.size
                 value = 6
    18-1-25 10:56:57 ===============================================================
    -- Gauges ----------------------------------------------------------------------
    metrics.GaugesTests.pending-job.size
                 value = 9
  2. Counter–>是Gauge的一个特例,维护一个计数器,可以通过inc()和dec()方法对计数器做修改。使用步骤与Gauge基本类似,在MetricRegistry中提供了静态方法可以直接实例化一个Counter.测试代码如下:

    package metrics;
    import java.util.LinkedList;
    import java.util.Queue;
    import java.util.concurrent.TimeUnit;
    import com.codahale.metrics.ConsoleReporter;
    import com.codahale.metrics.Counter;
    import com.codahale.metrics.MetricRegistry;
    public class CounterTest {
    
         /** * 实例化一个registry,最核心的一个模块,相当于一个应用程序的metrics系统的容器,维护一个Map */
        private static final MetricRegistry metrics = new MetricRegistry();
    
        /** * 在控制台上打印输出 */
        private static ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();
    
        /** * 实例化一个counter,同样可以通过如下方式进行实例化再注册进去 * pendingJobs = new Counter(); * metrics.register(MetricRegistry.name(TestCounter.class, "pending-jobs"), pendingJobs); */
        private static Counter pendingJobs = metrics.counter(MetricRegistry.name(CounterTest.class, "pedding-jobs"));
    
        private static Queue<String> queue = new LinkedList<String>();
    
        public static void add(String str) {
            pendingJobs.inc();
            queue.offer(str);
        }
    
        public String take() {
            pendingJobs.dec();
            return queue.poll();
        }
    
        public static void main(String[]args) throws InterruptedException {
            reporter.start(3, TimeUnit.SECONDS);
            while(true){
                add("1");
                Thread.sleep(1000);
            }
    
        }
    }

    打印结果如下:

    18-1-25 11:05:19 ===============================================================
    -- Counters --------------------------------------------------------------------
    metrics.CounterTest.pedding-jobs
                 count = 4
    18-1-25 11:05:22 ===============================================================
    -- Counters --------------------------------------------------------------------
    metrics.CounterTest.pedding-jobs
                 count = 6
    18-1-25 11:05:25 ===============================================================
    -- Counters --------------------------------------------------------------------
    metrics.CounterTest.pedding-jobs
                 count = 9
  3. Meters–> 用来度量某个时间段的平均处理次数(request per second),每1、5、15分钟的TPS。比如一个service的请求数,通过metrics.meter()实例化一个Meter之后,然后通过meter.mark()方法就能将本次请求记录下来。统计结果有总的请求数,平均每秒的请求数,以及最近的1、5、15分钟的平均TPS.测试代码如下:

    package metrics;
    import java.util.concurrent.TimeUnit;
    import com.codahale.metrics.ConsoleReporter;
    import com.codahale.metrics.Meter;
    import com.codahale.metrics.MetricRegistry;
    public class MetersTest {
    
        /** * 实例化一个registry,最核心的一个模块,相当于一个应用程序的metrics系统的容器,维护一个Map */
        private static final MetricRegistry metrics = new MetricRegistry();
    
        /** * 在控制台上打印输出 */
        private static ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();
    
        /** * 实例化一个Meter */
        private static final Meter requests = metrics.meter(MetricRegistry.name(MetersTest.class, "request"));
    
        public static void handleRequest() {
            requests.mark();
        }
    
        public static void main(String[] args) throws InterruptedException {
            reporter.start(3, TimeUnit.SECONDS);
            while (true) {
                handleRequest();
                Thread.sleep(100);
            }
        }
    }

    打印结果如下:

    18-1-25 11:09:17 ===============================================================
    -- Meters ----------------------------------------------------------------------
    metrics.MetersTest.request
                 count = 30
             mean rate = 9.95 events/second
         1-minute rate = 0.00 events/second
         5-minute rate = 0.00 events/second
        15-minute rate = 0.00 events/second
    18-1-25 11:09:20 ===============================================================
    -- Meters ----------------------------------------------------------------------
    metrics.MetersTest.request
                 count = 58
             mean rate = 9.66 events/second
         1-minute rate = 9.80 events/second
         5-minute rate = 9.80 events/second
        15-minute rate = 9.80 events/second
    
    
    18-1-25 11:09:23 ===============================================================
    -- Meters ----------------------------------------------------------------------
    metrics.MetersTest.request
                 count = 87
             mean rate = 9.66 events/second
         1-minute rate = 9.80 events/second
         5-minute rate = 9.80 events/second
        15-minute rate = 9.80 events/second

    其中数据统计的意义分别如下:

    • count–>总的请求数
    • mean rate–>平均每秒的请求数
    • 1-minute rate–> 最近1分钟的平均TPS
    • 5-minute rate–> 最近5分钟的平均TPS
    • 15-minute rate –> 最近15分钟的平均TPS
  4. Histograms–> 统计数据的分布情况,最大值、最小值、平均值、中位数,百分比(75%、90%、95%、98%、99%和99.9%)。例如,需要统计某个页面的请求响应时间分布情况,可以使用该种类型的Metrics进行统计.测试代码如下:

    package metrics;
    import java.util.Random;
    import java.util.concurrent.TimeUnit;
    import com.codahale.metrics.ConsoleReporter;
    import com.codahale.metrics.Histogram;
    import com.codahale.metrics.MetricRegistry;
    public class HistogramsTest {
    
        /** * 实例化一个registry,最核心的一个模块,相当于一个应用程序的metrics系统的容器,维护一个Map */
        private static final MetricRegistry metrics = new MetricRegistry();
    
        /** * 在控制台上打印输出 */
        private static ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();
    
        /** * 实例化一个Histograms */
        private static final Histogram randomNums = metrics.histogram(MetricRegistry.name(HistogramsTest.class, "random"));
    
        public static void handleRequest(double random) {
            randomNums.update((int) (random * 100));
        }
    
        public static void main(String[] args) throws InterruptedException {
            reporter.start(3, TimeUnit.SECONDS);
            Random rand = new Random();
            while (true) {
                handleRequest(rand.nextDouble());
                Thread.sleep(100);
            }
        }
    }

    打印结果如下:

    18-1-25 11:22:35 ===============================================================
    -- Histograms ------------------------------------------------------------------
    metrics.HistogramsTest.random
             count = 30 // 总数
               min = 1 // 最小值
               max = 99 // 最大值
              mean = 48.01 // 算术平均值
            stddev = 27.30 // 标准偏差
            median = 54.00 // 中位数
              75% <= 66.00
              95% <= 97.00
              98% <= 99.00
              99% <= 99.00
            99.9% <= 99.00
  5. Timers–> 用来统计某一块代码段的执行时间以及其分布情况,具体是基于Histograms和Meters来实现的。测试代码如下:

    package metrics;
    import java.util.Random;
    import java.util.concurrent.TimeUnit;
    import com.codahale.metrics.ConsoleReporter;
    import com.codahale.metrics.MetricRegistry;
    import com.codahale.metrics.Timer;
    public class TimersTest {
    
        /** * 实例化一个registry,最核心的一个模块,相当于一个应用程序的metrics系统的容器,维护一个Map */
        private static final MetricRegistry metrics = new MetricRegistry();
    
        /** * 在控制台上打印输出 */
        private static ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();
    
        /** * 实例化一个Meter */
        private static final Timer requests = metrics.timer(MetricRegistry.name(TimersTest.class, "request"));
    
        public static void handleRequest(int sleep) {
            Timer.Context context = requests.time();
            try {
                Thread.sleep(sleep);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                context.stop();
            }
    
        }
    
        public static void main(String[] args) throws InterruptedException {
            reporter.start(3, TimeUnit.SECONDS);
            Random random = new Random();
            while (true) {
                handleRequest(random.nextInt(1000));
            }
        }
    }

    打印结果如下:

    18-1-25 11:29:18 ===============================================================
    -- Timers ----------------------------------------------------------------------
    metrics.TimersTest.request
             count = 6
         mean rate = 1.99 calls/second
     1-minute rate = 0.00 calls/second
     5-minute rate = 0.00 calls/second
    15-minute rate = 0.00 calls/second
               min = 153.80 milliseconds
               max = 981.86 milliseconds
              mean = 458.88 milliseconds
            stddev = 265.74 milliseconds
            median = 331.81 milliseconds
              75% <= 546.66 milliseconds
              95% <= 981.86 milliseconds
              98% <= 981.86 milliseconds
              99% <= 981.86 milliseconds
            99.9% <= 981.86 milliseconds

参考链接如下:

第三十五章 metrics(3)- codahale-metrics基本使用

Metrics介绍和Spring的集成

codahale Metrics

Metrics-Java版的指标度量工具之一

DropwizardMetricServices

我们把视线回到DropwizardMetricServices.

  1. 字段如下:

    // com.codahale.metrics 中的实现
    private final MetricRegistry registry;
    
    // 由于spring boot 中没有ReservoirFactory的实现,因此这里使用的是ReservoirFactory的默认实现-->NONE
    private final ReservoirFactory reservoirFactory;
    
    // key-->GaugeName,value--> SimpleGauge
    private final ConcurrentMap<String, SimpleGauge> gauges = new ConcurrentHashMap<String, SimpleGauge>();
    
    // key--> Metric的名字,value --> MetricRegistry中注册的名字
    private final ConcurrentHashMap<String, String> names = new ConcurrentHashMap<String, String>();
  2. 构造器如下:

    public DropwizardMetricServices(MetricRegistry registry) {
        this(registry, null);
    }
    
    public DropwizardMetricServices(MetricRegistry registry,
            ReservoirFactory reservoirFactory) {
        this.registry = registry;
        this.reservoirFactory = (reservoirFactory == null ? ReservoirFactory.NONE
                : reservoirFactory);
    }
  3. 关于CounterService, GaugeService方法分别实现如下:

    1. increment,decrement 最终都是调用incrementInternal来实现的,区别在于传入incrementInternal方法value值分别为1,-1.incrementInternal代码如下:

      private void incrementInternal(String name, long value) {
          if (name.startsWith("meter")) {
              Meter meter = this.registry.meter(name);
              meter.mark(value);
          }
          else {
              name = wrapCounterName(name);
              Counter counter = this.registry.counter(name);
              counter.inc(value);
          }
      }
      1. 如果name是meter的开头的,则从MetricRegistry中实例化1个Meter,并调用mark方法将本次请求记录下来
      2. 否则将其当做Counter来处理

        1. 尝试为其加上counter.的前缀.代码如下:

          private String wrapCounterName(String metricName) {
                  return wrapName(metricName, "counter.");
          }

          调用:

          private String wrapName(String metricName, String prefix) {
              String cached = this.names.get(metricName);
              if (cached != null) {
                  return cached;
              }
              if (metricName.startsWith(prefix)) {
                  return metricName;
              }
              String name = prefix + metricName;
              this.names.put(metricName, name);
              return name;
          }
          1. 如果缓存中有的话,则直接返回对应的值
          2. 如果传入的metricName是指定前缀开头的,则直接返回.对于当前是counter.
          3. 为metricName 加上指定前缀,放入到names缓存中,然后进行返回
        2. MetricRegistry中实例化1个Counter,并调用inc,对计数器进行操作

    2. 这里首先先介绍1下DropwizardMetricServices中声明的抽象类–>MetricRegistrar(注册metrics的策略类).代码如下:

      private static abstract class MetricRegistrar<T extends Metric> {
          private final Class<T> type;
          @SuppressWarnings("unchecked")
          MetricRegistrar() {
              // 实例化时获得泛型参数
              this.type = (Class<T>) ResolvableType
                      .forClass(MetricRegistrar.class, getClass()).resolveGeneric();
          }
      
          public void checkExisting(Metric metric) {
              Assert.isInstanceOf(this.type, metric,
                      "Different metric type already registered");
          }
          protected abstract T register(MetricRegistry registry, String name);
      
          protected abstract T createForReservoir(Reservoir reservoir);
      }

      该抽象类有2个子类:

      1. TimerMetricRegistrar–>注册Timer的策略实现.泛型参数为Timer.代码如下:

        private static class TimerMetricRegistrar extends MetricRegistrar<Timer> {
            @Override
            protected Timer register(MetricRegistry registry, String name) {
                return registry.timer(name);
            }
            @Override
            protected Timer createForReservoir(Reservoir reservoir) {
                return new Timer(reservoir);
            }
        }
      2. HistogramMetricRegistrar–>注册Histogram的策略实现.代码如下:

        private static class HistogramMetricRegistrar extends MetricRegistrar<Histogram> {
            @Override
            protected Histogram register(MetricRegistry registry, String name) {
                return registry.histogram(name);
            }
            @Override
            protected Histogram createForReservoir(Reservoir reservoir) {
                return new Histogram(reservoir);
            }
        }
    3. submit,实现如下:

      public void submit(String name, double value) {
          // 1. 如果是histogram开头的,则当在Histogram来进行处理
          if (name.startsWith("histogram")) {
              submitHistogram(name, value);
          }
          // 2. 如果是timer开头的,则当在Timer来进行处理
          else if (name.startsWith("timer")) {
              submitTimer(name, value);
          }
          else {
              // 3. 否则,当做Gauge来处理
              name = wrapGaugeName(name);
              setGaugeValue(name, value);
          }
      }
      1. 如果是histogram开头的,则当在Histogram来进行处理.代码如下:

        private void submitHistogram(String name, double value) {
            long longValue = (long) value;
            Histogram metric = register(name, new HistogramMetricRegistrar());
            metric.update(longValue);
        }
        1. 实例化或者获得对应的Histogram.代码如下:

          private <T extends Metric> T register(String name, MetricRegistrar<T> registrar) {
              // 1. 从reservoirFactory 中获取对应的Reservoir,默认返回null
              Reservoir reservoir = this.reservoirFactory.getReservoir(name);
              if (reservoir == null) {
                  // 2. 如果为null,则向MetricRegistry进行注册进行注册,默认都会执行到这里
                  return registrar.register(this.registry, name);
              }
              // 3. 从MetricRegistry获得该名字所对应的Metric,如果存在的话,则进行判断是否是所需要的类型,然后进行返回
              Metric metric = this.registry.getMetrics().get(name);
              if (metric != null) {
                  registrar.checkExisting(metric);
                  return (T) metric;
              }
              try {
                  // 4. 如果没找到,则实例化1个进行注册.
                  return this.registry.register(name, registrar.createForReservoir(reservoir));
              }
              catch (IllegalArgumentException ex) {
                  // 5. 当抛出IllegalArgumentException异常时,则意味着该名字所对应的Metric已经注册,进行判断是否是所需要的类型,然后进行返回
                  Metric added = this.registry.getMetrics().get(name);
                  registrar.checkExisting(added);
                  return (T) added;
              }
          }
          1. 从reservoirFactory 中获取对应的Reservoir,默认返回null
          2. 如果为null,则向MetricRegistry进行注册进行注册,默认都会执行到这里.在当前场景下,是执行HistogramMetricRegistrar#register,直接实例化1个Histogram
          3. 从MetricRegistry获得该名字所对应的Metric,如果存在的话,则进行判断是否是所需要的类型,然后进行返回
          4. 如果没找到,则实例化1个进行注册.
          5. 当抛出IllegalArgumentException异常时,则意味着该名字所对应的Metric已经注册,进行判断是否是所需要的类型,然后进行返回
        2. 进行更新
      2. 如果是timer开头的,则当在Timer来进行处理.代码如下:

        private void submitTimer(String name, double value) {
            long longValue = (long) value;
            Timer metric = register(name, new TimerMetricRegistrar());
            metric.update(longValue, TimeUnit.MILLISECONDS);
        }
        1. 实例化或者获得对应的Timer.具体步骤和submitHistogram中的差不多,这里不在赘述
        2. 进行更新
      3. 否则,当做Gauge来处理.

        1. 尝试为其加上gauge.的前缀.代码如下:

          private String wrapGaugeName(String metricName) {
              return wrapName(metricName, "gauge.");
          }

          关于wrapName方法,我们之前已经介绍过了.

        2. 设置Gauge的值.代码如下:

          private void setGaugeValue(String name, double value) {
          
              SimpleGauge gauge = this.gauges.get(name);
              if (gauge == null) {
                  SimpleGauge newGauge = new SimpleGauge(value);
                  gauge = this.gauges.putIfAbsent(name, newGauge);
                  if (gauge == null) {
                      // 进行注册
                      this.registry.register(name, newGauge);
                      return;
                  }
              }
              gauge.setValue(value);
          }
          1. 从缓存中获得对应的SimpleGauge
          2. 如果不存在,则实例化SimpleGauge,放入缓存中.

            1. 如果此时是第一次添加到缓存中,则进行注册,否则,执行第3步
          3. 设置值
    4. reset,实现如下:

      public void reset(String name) {
          if (!name.startsWith("meter")) {
              name = wrapCounterName(name);
          }
          this.registry.remove(name);
      }
      1. 如果不是meter开头的,则尝试为其加上 counter. 前缀
      2. 从MetricRegistry 删除
  4. 自动装配:

    声明在MetricsDropwizardAutoConfiguration中.

    1. 由于 MetricsDropwizardAutoConfiguration声明了如下注解:

      @Configuration
      @ConditionalOnClass(MetricRegistry.class)
      @AutoConfigureBefore(MetricRepositoryAutoConfiguration.class)
      • @Configuration–> 配置类
      • @ConditionalOnClass(MetricRegistry.class) –> 在当前类路径下存在com.codahale.metrics.MetricRegistry,也就是依赖了metrics-core jar 时生效.
      • @AutoConfigureBefore(MetricRepositoryAutoConfiguration.class)–> 在MetricRepositoryAutoConfiguration之后生效.**如果该配置生效了,则不在注册MetricRepositoryAutoConfiguration的配置的bean(DefaultCounterService,GaugeBuffers等…)
        **

      由于我们加入了 metrics-core的依赖,因此,此时该配置生效

    2. 在该配置中声明了如下3个bean:

      1. MetricRegistry–> 代码如下:

        @Bean
        @ConditionalOnMissingBean
        public MetricRegistry metricRegistry() {
            return new MetricRegistry();
        }
        • @Bean –> 注册1个id 为metricRegistry,类型为MetricRegistry的bean
        • @ConditionalOnMissingBean–> 当BeanFactory中不存在MetricRegistry类型的bean时生效.
      2. DropwizardMetricServices–>代码如下:

        @Bean
        @ConditionalOnMissingBean({ DropwizardMetricServices.class, CounterService.class,
                GaugeService.class })
        public DropwizardMetricServices dropwizardMetricServices(
                MetricRegistry metricRegistry) {
            if (this.reservoirFactory == null) {
                return new DropwizardMetricServices(metricRegistry);
            }
            else {
                return new DropwizardMetricServices(metricRegistry, this.reservoirFactory);
            }
        }
        • @Bean–> 注册1个id为dropwizardMetricServices,类型为DropwizardMetricServices的bean
        • @ConditionalOnMissingBean({ DropwizardMetricServices.class, CounterService.class,
          GaugeService.class })–> 当BeanFactory中不存在DropwizardMetricServices,CounterService,GaugeService类型的bean时注册.
      3. MetricReaderPublicMetrics,代码如下:

        @Bean
        public MetricReaderPublicMetrics dropwizardPublicMetrics(
                MetricRegistry metricRegistry) {
            MetricRegistryMetricReader reader = new MetricRegistryMetricReader(
                    metricRegistry);
            return new MetricReaderPublicMetrics(reader);
        }
        • @Bean –> 注册1个id为 dropwizardPublicMetrics,类型为MetricReaderPublicMetrics的bean.
  5. 使用案例:

    由于DropwizardMetricServices实现了CounterService, GaugeService,因此,在两篇文章中的示例不需要进行修改,就能使用,此时,他们注入的实例是DropwizardMetricServices

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_26000415/article/details/79160557
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞