前言
本文我们来介绍一下DropwizardMetricServices,该类实现了CounterService, GaugeService.
解析
DropwizardMetricServices–> 基于如下规则进行处理:
- 如果 increment 方法传入的名字是meter.开头的,则通过Meter来处理
- 其他的,则当做Counter 来处理
- 如果传入submit的names 所对应的参数值是histogram.开头的,则当做Histogram进行处理
- 如果传入submit的names 所对应的参数值是timer.开头的,则当做Timer进行处理
- 如果是其他,则当做Gauge 处理
由于其使用MetricRegistry实现功能的.这里有必要介绍一下
MetricRegistry介绍
MetricRegistry中有5种数据类型,spring boot中我们可以加入如下依赖:
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
</dependency>
方便做以下各种数据类型的测试.
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
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
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
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
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基本使用
DropwizardMetricServices
我们把视线回到DropwizardMetricServices.
字段如下:
// 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>();
构造器如下:
public DropwizardMetricServices(MetricRegistry registry) { this(registry, null); } public DropwizardMetricServices(MetricRegistry registry, ReservoirFactory reservoirFactory) { this.registry = registry; this.reservoirFactory = (reservoirFactory == null ? ReservoirFactory.NONE : reservoirFactory); }
关于CounterService, GaugeService方法分别实现如下:
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); } }
- 如果name是meter的开头的,则从MetricRegistry中实例化1个Meter,并调用mark方法将本次请求记录下来
否则将其当做Counter来处理
尝试为其加上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; }
- 如果缓存中有的话,则直接返回对应的值
- 如果传入的metricName是指定前缀开头的,则直接返回.对于当前是counter.
- 为metricName 加上指定前缀,放入到names缓存中,然后进行返回
MetricRegistry中实例化1个Counter,并调用inc,对计数器进行操作
这里首先先介绍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个子类:
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); } }
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); } }
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); } }
如果是histogram开头的,则当在Histogram来进行处理.代码如下:
private void submitHistogram(String name, double value) { long longValue = (long) value; Histogram metric = register(name, new HistogramMetricRegistrar()); metric.update(longValue); }
实例化或者获得对应的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; } }
- 从reservoirFactory 中获取对应的Reservoir,默认返回null
- 如果为null,则向MetricRegistry进行注册进行注册,默认都会执行到这里.在当前场景下,是执行HistogramMetricRegistrar#register,直接实例化1个Histogram
- 从MetricRegistry获得该名字所对应的Metric,如果存在的话,则进行判断是否是所需要的类型,然后进行返回
- 如果没找到,则实例化1个进行注册.
- 当抛出IllegalArgumentException异常时,则意味着该名字所对应的Metric已经注册,进行判断是否是所需要的类型,然后进行返回
- 进行更新
如果是timer开头的,则当在Timer来进行处理.代码如下:
private void submitTimer(String name, double value) { long longValue = (long) value; Timer metric = register(name, new TimerMetricRegistrar()); metric.update(longValue, TimeUnit.MILLISECONDS); }
- 实例化或者获得对应的Timer.具体步骤和submitHistogram中的差不多,这里不在赘述
- 进行更新
否则,当做Gauge来处理.
尝试为其加上gauge.的前缀.代码如下:
private String wrapGaugeName(String metricName) { return wrapName(metricName, "gauge."); }
关于wrapName方法,我们之前已经介绍过了.
设置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); }
- 从缓存中获得对应的SimpleGauge
如果不存在,则实例化SimpleGauge,放入缓存中.
- 如果此时是第一次添加到缓存中,则进行注册,否则,执行第3步
- 设置值
reset,实现如下:
public void reset(String name) { if (!name.startsWith("meter")) { name = wrapCounterName(name); } this.registry.remove(name); }
- 如果不是meter开头的,则尝试为其加上 counter. 前缀
- 从MetricRegistry 删除
自动装配:
声明在MetricsDropwizardAutoConfiguration中.
由于 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的依赖,因此,此时该配置生效
在该配置中声明了如下3个bean:
MetricRegistry–> 代码如下:
@Bean @ConditionalOnMissingBean public MetricRegistry metricRegistry() { return new MetricRegistry(); }
- @Bean –> 注册1个id 为metricRegistry,类型为MetricRegistry的bean
- @ConditionalOnMissingBean–> 当BeanFactory中不存在MetricRegistry类型的bean时生效.
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时注册.
MetricReaderPublicMetrics,代码如下:
@Bean public MetricReaderPublicMetrics dropwizardPublicMetrics( MetricRegistry metricRegistry) { MetricRegistryMetricReader reader = new MetricRegistryMetricReader( metricRegistry); return new MetricReaderPublicMetrics(reader); }
- @Bean –> 注册1个id为 dropwizardPublicMetrics,类型为MetricReaderPublicMetrics的bean.
使用案例:
由于DropwizardMetricServices实现了CounterService, GaugeService,因此,在两篇文章中的示例不需要进行修改,就能使用,此时,他们注入的实例是DropwizardMetricServices