Contents

Metrics 简介

1. 简介

Metrics 是一个 Java 库,它为 Java 应用程序提供测量工具。

它有几个模块,在本文中,我们将详细介绍metrics-core模块、metrics-healthchecks模块、metrics-servlets模块和metrics-servlet模块,并将其余部分勾勒出来,供您参考。

2. 模块metrics-core

2.1. Maven 依赖项

要使用metrics-core模块,只需将一个依赖项添加到pom.xml文件中:

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

你可以在这里 找到它的最新版本。

2.2. MetricRegistry

简单地说,我们将使用MetricRegistry类来注册一个或多个指标。

我们可以为所有指标使用一个指标注册表,但如果我们想对不同的指标使用不同的报告方法,我们还可以将指标分组并为每个组使用不同的指标注册表。

现在让我们创建一个MetricRegistry

MetricRegistry metricRegistry = new MetricRegistry();

然后我们可以用这个MetricRegistry注册一些指标:

Meter meter1 = new Meter();
metricRegistry.register("meter1", meter1);
Meter meter2 = metricRegistry.meter("meter2");

有两种创建新指标的基本方法:自己实例化一个或从指标注册表中获取一个。如您所见,我们在上面的示例中同时使用了它们,我们正在实例化Meter对象“meter1”,我们正在获取另一个Meter对象“meter2”,它由metricRegistry创建。

在度量注册表中,每个度量都有一个唯一的名称,因为我们在上面使用“meter1”和“meter2”作为度量名称。MetricRegistry还提供了一组静态辅助方法来帮助我们创建正确的度量名称:

String name1 = MetricRegistry.name(Filter.class, "request", "count");
String name2 = MetricRegistry.name("CustomFilter", "response", "count");

如果我们需要管理一组度量注册表,我们可以使用SharedMetricRegistries类,它是一个单例和线程安全的。我们可以在其中添加一个度量寄存器,从中检索该度量寄存器,然后将其删除:

SharedMetricRegistries.add("default", metricRegistry);
MetricRegistry retrievedMetricRegistry = SharedMetricRegistries.getOrCreate("default");
SharedMetricRegistries.remove("default");

3. 度量概念

metrics-core 模块提供了几种常用的度量类型:MeterGaugeCounterHistogramTimer以及Reporter以输出度量值。

3.1. Meter

Meter测量事件发生的次数和速率:

Meter meter = new Meter();
long initCount = meter.getCount();
assertThat(initCount, equalTo(0L));
meter.mark();
assertThat(meter.getCount(), equalTo(1L));
meter.mark(20);
assertThat(meter.getCount(), equalTo(21L));
double meanRate = meter.getMeanRate();
double oneMinRate = meter.getOneMinuteRate();
double fiveMinRate = meter.getFiveMinuteRate();
double fifteenMinRate = meter.getFifteenMinuteRate(); 

*getCount()*方法返回事件发生计数,mark()方法将事件发生计数加1 或 n。Meter对象提供了四种速率,分别代表整个Meter生命周期、最近一分钟、最近五分钟和最近一个季度的平均速率。

3.2. Gauge

Gauge是一个简单地用于返回特定值的接口。metrics-core 模块提供了它的几个实现:RatioGaugeCachedGaugeDerivativeGaugeJmxAttributeGauge

RatioGauge是一个抽象类,它测量一个值与另一个值的比率。

让我们看看如何使用它。首先,我们实现一个类AttendanceRatioGauge

public class AttendanceRatioGauge extends RatioGauge {
    private int attendanceCount;
    private int courseCount;
    @Override
    protected Ratio getRatio() {
        return Ratio.of(attendanceCount, courseCount);
    }
    
    // standard constructors
}

然后我们对其进行测试:

RatioGauge ratioGauge = new AttendanceRatioGauge(15, 20);
assertThat(ratioGauge.getValue(), equalTo(0.75));

CachedGauge是另一个可以缓存值的抽象类,因此,当值计算成本很高时,它非常有用。要使用它,我们需要实现一个类ActiveUsersGauge

public class ActiveUsersGauge extends CachedGauge<List<Long>> {
    
    @Override
    protected List<Long> loadValue() {
        return getActiveUserCount();
    }
 
    private List<Long> getActiveUserCount() {
        List<Long> result = new ArrayList<Long>();
        result.add(12L);
        return result;
    }
    // standard constructors
}

然后我们对其进行测试,看看它是否按预期工作:

Gauge<List<Long>> activeUsersGauge = new ActiveUsersGauge(15, TimeUnit.MINUTES);
List<Long> expected = new ArrayList<>();
expected.add(12L);
assertThat(activeUsersGauge.getValue(), equalTo(expected));

在实例化ActiveUsersGauge时,我们将缓存的过期时间设置为 15 分钟。

DerivativeGauge也是一个抽象类,它允许您从其他Gauge派生一个值作为其值。

让我们看一个例子:

public class ActiveUserCountGauge extends DerivativeGauge<List<Long>, Integer> {
    
    @Override
    protected Integer transform(List<Long> value) {
        return value.size();
    }
    // standard constructors
}

GaugeActiveUsersGauge派生其值,因此我们希望它是来自基本列表大小的值:

Gauge<List<Long>> activeUsersGauge = new ActiveUsersGauge(15, TimeUnit.MINUTES);
Gauge<Integer> activeUserCountGauge = new ActiveUserCountGauge(activeUsersGauge);
assertThat(activeUserCountGauge.getValue(), equalTo(1));

当我们需要访问通过 JMX 公开的其他库的指标时,使用JmxAttributeGauge

3.3. Counter

Counter用于记录增量和减量:

Counter counter = new Counter();
long initCount = counter.getCount();
assertThat(initCount, equalTo(0L));
counter.inc();
assertThat(counter.getCount(), equalTo(1L));
counter.inc(11);
assertThat(counter.getCount(), equalTo(12L));
counter.dec();
assertThat(counter.getCount(), equalTo(11L));
counter.dec(6);
assertThat(counter.getCount(), equalTo(5L));

3.4. Histogram

Histogram用于跟踪Long值流,并分析它们的统计特征,例如最大值、最小值、平均值、中值、标准差、第 75 个百分位数等:

Histogram histogram = new Histogram(new UniformReservoir());
histogram.update(5);
long count1 = histogram.getCount();
assertThat(count1, equalTo(1L));
Snapshot snapshot1 = histogram.getSnapshot();
assertThat(snapshot1.getValues().length, equalTo(1));
assertThat(snapshot1.getValues()[0], equalTo(5L));
histogram.update(20);
long count2 = histogram.getCount();
assertThat(count2, equalTo(2L));
Snapshot snapshot2 = histogram.getSnapshot();
assertThat(snapshot2.getValues().length, equalTo(2));
assertThat(snapshot2.getValues()[1], equalTo(20L));
assertThat(snapshot2.getMax(), equalTo(20L));
assertThat(snapshot2.getMean(), equalTo(12.5));
assertEquals(10.6, snapshot2.getStdDev(), 0.1);
assertThat(snapshot2.get75thPercentile(), equalTo(20.0));
assertThat(snapshot2.get999thPercentile(), equalTo(20.0));

Histogram通过使用储层采样对数据进行采样,当我们实例化一个Histogram对象时,我们需要显式地设置它的储层。

Reservoir是一个接口,metrics-core 提供了它们的四种实现:ExponentiallyDecayingReservoirUniformReservoirSlidingTimeWindowReservoirSlidingWindowReservoir

在上一节中,我们提到了MetricRegistry除了使用构造函数之外,还可以创建度量。当我们使用metricRegistry.histogram()时,它返回一个带有ExponentiallyDecayingReservoir实现的Histogram实例。

3.5. Timer

Timer用于跟踪由Context对象表示的多个计时持续时间,它还提供它们的统计数据:

Timer timer = new Timer();
Timer.Context context1 = timer.time();
TimeUnit.SECONDS.sleep(5);
long elapsed1 = context1.stop();
assertEquals(5000000000L, elapsed1, 1000000);
assertThat(timer.getCount(), equalTo(1L));
assertEquals(0.2, timer.getMeanRate(), 0.1);
Timer.Context context2 = timer.time();
TimeUnit.SECONDS.sleep(2);
context2.close();
assertThat(timer.getCount(), equalTo(2L));
assertEquals(0.3, timer.getMeanRate(), 0.1);

3.6. Reporter

当我们需要输出我们的测量结果时,我们可以使用Reporter。这是一个接口,metrics-core 模块提供了它的几种实现,例如ConsoleReporterCsvReporterSlf4jReporterJmxReporter 等。

这里我们以ConsoleReporter为例:

MetricRegistry metricRegistry = new MetricRegistry();
Meter meter = metricRegistry.meter("meter");
meter.mark();
meter.mark(200);
Histogram histogram = metricRegistry.histogram("histogram");
histogram.update(12);
histogram.update(17);
Counter counter = metricRegistry.counter("counter");
counter.inc();
counter.dec();
ConsoleReporter reporter = ConsoleReporter.forRegistry(metricRegistry).build();
reporter.start(5, TimeUnit.MICROSECONDS);
reporter.report();

以下是ConsoleReporter 的示例输出:

-- Histograms ------------------------------------------------------------------
histogram
count = 2
min = 12
max = 17
mean = 14.50
stddev = 2.50
median = 17.00
75% <= 17.00
95% <= 17.00
98% <= 17.00
99% <= 17.00
99.9% <= 17.00
-- Meters ----------------------------------------------------------------------
meter
count = 201
mean rate = 1756.87 events/second
1-minute rate = 0.00 events/second
5-minute rate = 0.00 events/second
15-minute rate = 0.00 events/second

4. 模块metrics-healthchecks

Metrics 有一个扩展 metrics-healthchecks 模块,用于处理健康检查。

4.1. Maven 依赖项

要使用 metrics-healthchecks 模块,我们需要将此依赖项添加到pom.xml文件中:

<dependency>
    <groupId>io.dropwizard.metrics</groupId>
    <artifactId>metrics-healthchecks</artifactId>
    <version>3.1.2</version>
</dependency>

你可以在这里 找到它的最新版本。

4.2. 用法

首先,我们需要几个类负责具体的健康检查操作,这些类必须实现HealthCheck

例如,我们使用DatabaseHealthCheckUserCenterHealthCheck

public class DatabaseHealthCheck extends HealthCheck {
 
    @Override
    protected Result check() throws Exception {
        return Result.healthy();
    }
}
public class UserCenterHealthCheck extends HealthCheck {
 
    @Override
    protected Result check() throws Exception {
        return Result.healthy();
    }
}

然后,我们需要一个HealthCheckRegistry(就像MetricRegistry一样),并用它注册DatabaseHealthCheckUserCenterHealthCheck

HealthCheckRegistry healthCheckRegistry = new HealthCheckRegistry();
healthCheckRegistry.register("db", new DatabaseHealthCheck());
healthCheckRegistry.register("uc", new UserCenterHealthCheck());
assertThat(healthCheckRegistry.getNames().size(), equalTo(2));

我们也可以注销HealthCheck

healthCheckRegistry.unregister("uc");
 
assertThat(healthCheckRegistry.getNames().size(), equalTo(1));

我们可以运行所有的HealthCheck实例:

Map<String, HealthCheck.Result> results = healthCheckRegistry.runHealthChecks();
for (Map.Entry<String, HealthCheck.Result> entry : results.entrySet()) {
    assertThat(entry.getValue().isHealthy(), equalTo(true));
}

最后,我们可以运行一个特定的HealthCheck实例:

healthCheckRegistry.runHealthCheck("db");

5. 模块metrics-servlet

Metrics 为我们提供了一些有用的 servlet,它们允许我们通过 HTTP 请求访问与指标相关的数据。

5.1. Maven 依赖项

要使用 metrics-servlets 模块,我们需要将此依赖项添加到pom.xml文件中:

<dependency>
    <groupId>io.dropwizard.metrics</groupId>
    <artifactId>metrics-servlets</artifactId>
    <version>3.1.2</version>
</dependency>

你可以在这里 找到它的最新版本。

5.2. HealthCheckServlet用法

HealthCheckServlet提供健康检查结果。首先,我们需要创建一个ServletContextListener来公开我们的HealthCheckRegistry

public class MyHealthCheckServletContextListener
  extends HealthCheckServlet.ContextListener {
 
    public static HealthCheckRegistry HEALTH_CHECK_REGISTRY
      = new HealthCheckRegistry();
    static {
        HEALTH_CHECK_REGISTRY.register("db", new DatabaseHealthCheck());
    }
    @Override
    protected HealthCheckRegistry getHealthCheckRegistry() {
        return HEALTH_CHECK_REGISTRY;
    }
}

然后,我们将此侦听器和HealthCheckServlet添加到web.xml文件中:

<listener>
    <listener-class>com.blogdemo.metrics.servlets.MyHealthCheckServletContextListener</listener-class>
</listener>
<servlet>
    <servlet-name>healthCheck</servlet-name>
    <servlet-class>com.codahale.metrics.servlets.HealthCheckServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>healthCheck</servlet-name>
    <url-pattern>/healthcheck</url-pattern>
</servlet-mapping>

现在我们可以启动 Web 应用程序,向“http://localhost:8080/healthcheck” 发送 GET 请求以获取健康检查结果。它的响应应该是这样的:

{
  "db": {
    "healthy": true
  }
}

5.3. ThreadDumpServlet用法

ThreadDumpServlet提供有关 JVM 中所有活动线程、它们的状态、它们的堆栈跟踪以及它们可能正在等待的任何锁的状态的信息。

如果我们想使用它,我们只需将这些添加到web.xml文件中:

<servlet>
    <servlet-name>threadDump</servlet-name>
    <servlet-class>com.codahale.metrics.servlets.ThreadDumpServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>threadDump</servlet-name>
    <url-pattern>/threaddump</url-pattern>
</servlet-mapping>

线程转储数据将在“http://localhost:8080/threaddump” 中提供。

5.4. PingServlet用法

PingServlet可用于测试应用程序是否正在运行。我们将这些添加到web.xml文件中:

<servlet>
    <servlet-name>ping</servlet-name>
    <servlet-class>com.codahale.metrics.servlets.PingServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ping</servlet-name>
    <url-pattern>/ping</url-pattern>
</servlet-mapping>

然后向“http://localhost:8080/ping” 发送 GET 请求。响应的状态码是 200,其内容是“pong”。

5.5. MetricsServlet使用

MetricsServlet提供度量数据。首先,我们需要创建一个ServletContextListener来公开我们的MetricRegistry

public class MyMetricsServletContextListener
  extends MetricsServlet.ContextListener {
    private static MetricRegistry METRIC_REGISTRY
     = new MetricRegistry();
    static {
        Counter counter = METRIC_REGISTRY.counter("m01-counter");
        counter.inc();
        Histogram histogram = METRIC_REGISTRY.histogram("m02-histogram");
        histogram.update(5);
        histogram.update(20);
        histogram.update(100);
    }
    @Override
    protected MetricRegistry getMetricRegistry() {
        return METRIC_REGISTRY;
    }
}

这个监听器和MetricsServlet 都需要添加到web.xml中:

<listener>
    <listener-class>com.codahale.metrics.servlets.MyMetricsServletContextListener</listener-class>
</listener>
<servlet>
    <servlet-name>metrics</servlet-name>
    <servlet-class>com.codahale.metrics.servlets.MetricsServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>metrics</servlet-name>
    <url-pattern>/metrics</url-pattern>
</servlet-mapping>

这将在我们的 Web 应用程序中公开,地址为“http://localhost:8080/metrics” 。它的响应应该包含各种指标数据:

{
  "version": "3.0.0",
  "gauges": {},
  "counters": {
    "m01-counter": {
      "count": 1
    }
  },
  "histograms": {
    "m02-histogram": {
      "count": 3,
      "max": 100,
      "mean": 41.66666666666666,
      "min": 5,
      "p50": 20,
      "p75": 100,
      "p95": 100,
      "p98": 100,
      "p99": 100,
      "p999": 100,
      "stddev": 41.69998667732268
    }
  },
  "meters": {},
  "timers": {}
}

5.6. AdminServlet使用

AdminServlet聚合HealthCheckServletThreadDumpServletMetricsServletPingServlet。 让我们将这些添加到web.xml中:

<servlet>
    <servlet-name>admin</servlet-name>
    <servlet-class>com.codahale.metrics.servlets.AdminServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>admin</servlet-name>
    <url-pattern>/admin/*</url-pattern>
</servlet-mapping>

现在可以通过“http://localhost:8080/admin” 访问它。我们将得到一个包含四个链接的页面,每个链接对应这四个 servlet。

请注意,如果我们想要进行健康检查和访问指标数据,仍然需要这两个侦听器。

6. 模块metrics-servlet

metrics-servlet模块提供了一个filter,它有几个指标:状态码的计量器、活动请求数量的计数器和请求持续时间的计时器。

6.1.Maven 依赖项

要使用此模块,我们首先将依赖项添加到pom.xml中:

<dependency>
    <groupId>io.dropwizard.metrics</groupId>
    <artifactId>metrics-servlet</artifactId>
    <version>3.1.2</version>
</dependency>

你可以在这里 找到它的最新版本。

6.2. 用法

要使用它,我们需要创建一个ServletContextListener,它将我们的MetricRegistry暴露给InstrumentedFilter

public class MyInstrumentedFilterContextListener
  extends InstrumentedFilterContextListener {
 
    public static MetricRegistry REGISTRY = new MetricRegistry();
    @Override
    protected MetricRegistry getMetricRegistry() {
        return REGISTRY;
    }
}

然后,我们将这些添加到web.xml中:

<listener>
     <listener-class>
         com.blogdemo.metrics.servlet.MyInstrumentedFilterContextListener
     </listener-class>
</listener>
<filter>
    <filter-name>instrumentFilter</filter-name>
    <filter-class>
        com.codahale.metrics.servlet.InstrumentedFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>instrumentFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

现在InstrumentedFilter可以工作了。如果我们想访问它的指标数据,我们可以通过它的MetricRegistry REGISTRY来实现。

7. 其他模块

除了我们上面介绍的模块,Metrics 还有一些其他的模块用于不同的目的:

  • metrics-jvm:为检测 JVM 内部提供了几个有用的指标
  • metrics-ehcache:提供InstrumentedEhcache,一个 Ehcache 缓存的装饰器
  • metrics-httpclient:提供用于检测 Apache HttpClient 的类(4.x 版本)
  • metrics-log4j:提供InstrumentedAppender,一个 Log4j Appender实现 log4j 1.x,它通过日志级别记录日志事件的速率
  • metrics-log4j2:类似于 metrics-log4j,仅适用于 log4j 2.x
  • metrics-logback:提供InstrumentedAppender,一个 Logback Appender实现,它通过日志级别记录日志事件的速率
  • metrics-json :为 Jackson提供HealthCheckModuleMetricsModule

更重要的是,除了这些主要的项目模块,其他一些第三方库 提供了与其他库和框架的集成。