spring boot 源码解析39-DropwizardMetricServices详解

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

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

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

前言

本文我们来介绍一下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 queue = new LinkedBlockingDeque();
    
            /** * 在控制台上打印输出 */
            private static ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics).build();
    
            public static void main(String[] args) throws InterruptedException {
                reporter.start(3, TimeUnit.SECONDS);
    
                //实例化一个Gauge
                Gauge gauge = new Gauge() {
                    @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 queue = new LinkedList();
    
            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 gauges = new ConcurrentHashMap();
    
        // key--> Metric的名字,value --> MetricRegistry中注册的名字
        private final ConcurrentHashMap names = new ConcurrentHashMap();
  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 {
              private final Class type;
              @SuppressWarnings("unchecked")
              MetricRegistrar() {
                  // 实例化时获得泛型参数
                  this.type = (Class) 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 {
                @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 {
                @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 register(String name, MetricRegistrar 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


来源:[]()

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

评论 抢沙发

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

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

支付宝扫一扫打赏

微信扫一扫打赏