1. 简介

Metrics 是一个为 Java 应用提供测量工具的库。它包含多个模块,本文将重点介绍以下核心模块:

  • metrics-core:核心指标功能
  • metrics-healthchecks:健康检查
  • metrics-servlets:Servlet 集成
  • metrics-servlet:请求过滤指标

其他模块也会简要概述,供参考。

2. 模块 metrics-core

2.1 Maven 依赖

只需在 pom.xml 中添加一个依赖:

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

最新版本可在 Maven 仓库 查询。

2.2 MetricRegistry

简单说,MetricRegistry 用于注册一个或多个指标。✅ 推荐用法:

  • 全局使用单一注册表
  • 若需不同报告方式,可分组使用多个注册表

创建注册表:

MetricRegistry metricRegistry = new MetricRegistry();

注册指标有两种方式:

// 方式1:手动创建实例
Meter meter1 = new Meter();
metricRegistry.register("meter1", meter1);

// 方式2:通过注册表创建(推荐)
Meter meter2 = metricRegistry.meter("meter2");

⚠️ 每个指标必须有唯一名称。注册表提供静态方法辅助命名:

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

管理多个注册表时,使用线程安全的单例 SharedMetricRegistries

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

3. 核心指标类型

metrics-core 提供六种核心指标类型:

3.1 Meter 事件速率测量

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

Meter meter = new Meter();
long initCount = meter.getCount(); // 初始为0
assertThat(initCount, equalTo(0L));

meter.mark(); // 标记1次事件
assertThat(meter.getCount(), equalTo(1L));

meter.mark(20); // 标记20次事件
assertThat(meter.getCount(), equalTo(21L));

// 获取不同时间窗口的平均速率
double meanRate = meter.getMeanRate();        // 全生命周期平均速率
double oneMinRate = meter.getOneMinuteRate(); // 最近1分钟平均速率
double fiveMinRate = meter.getFiveMinuteRate(); // 最近5分钟平均速率
double fifteenMinRate = meter.getFifteenMinuteRate(); // 最近15分钟平均速率

3.2 Gauge 即时值获取

Gauge 是一个接口,用于返回特定值。核心实现包括:

  • RatioGauge:比率计算
  • CachedGauge:带缓存的值
  • DerivativeGauge:派生值
  • JmxAttributeGauge:JMX 属性访问

比率计算示例

public class AttendanceRatioGauge extends RatioGauge {
    private int attendanceCount;
    private int courseCount;

    @Override
    protected Ratio getRatio() {
        return Ratio.of(attendanceCount, courseCount);
    }
    // 构造方法省略...
}

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

缓存值示例(适合计算开销大的场景):

public class ActiveUsersGauge extends CachedGauge<List<Long>> {
    @Override
    protected List<Long> loadValue() {
        return getActiveUserCount(); // 模拟耗时操作
    }
    private List<Long> getActiveUserCount() {
        return Arrays.asList(12L); // 模拟返回值
    }
    // 构造方法省略...
}

// 测试(缓存15分钟)
Gauge<List<Long>> gauge = new ActiveUsersGauge(15, TimeUnit.MINUTES);
assertThat(gauge.getValue(), equalTo(Arrays.asList(12L)));

3.3 Counter 计数器

记录增减操作:

Counter counter = new Counter();
counter.inc();      // +1
counter.inc(11);    // +11
counter.dec();      // -1
counter.dec(6);     // -6
assertThat(counter.getCount(), equalTo(5L)); // 1+11-1-6=5

3.4 Histogram 直方图

跟踪 Long 值流并计算统计特征(最大值、最小值、均值、百分位等):

Histogram histogram = new Histogram(new UniformReservoir());
histogram.update(5);
histogram.update(20);

Snapshot snapshot = histogram.getSnapshot();
assertThat(snapshot.getMax(), equalTo(20L));        // 最大值
assertThat(snapshot.getMean(), equalTo(12.5));      // 均值
assertThat(snapshot.get75thPercentile(), equalTo(20.0)); // 75百分位
assertThat(snapshot.getStdDev(), closeTo(10.6, 0.1)); // 标准差

⚠️ 需显式指定采样器(Reservoir),常用实现:

  • ExponentiallyDecayingReservoir:指数衰减采样(默认)
  • UniformReservoir:均匀采样
  • SlidingTimeWindowReservoir:滑动时间窗口
  • SlidingWindowReservoir:滑动窗口

3.5 Timer 计时器

跟踪多个时间段的统计信息:

Timer timer = new Timer();
Timer.Context context = timer.time();
TimeUnit.SECONDS.sleep(5); // 模拟耗时操作
long elapsedNanos = context.stop(); // 纳秒精度

assertThat(timer.getCount(), equalTo(1L));
assertThat(timer.getMeanRate(), closeTo(0.2, 0.1)); // 平均速率

3.6 Reporter 指标报告器

输出指标数据的接口,核心实现:

  • ConsoleReporter:控制台输出
  • CsvReporter:CSV 文件输出
  • Slf4jReporter:日志输出
  • JmxReporter:JMX 输出

控制台输出示例

MetricRegistry registry = new MetricRegistry();
registry.meter("meter").mark(200);
registry.histogram("histogram").update(12);

ConsoleReporter reporter = ConsoleReporter.forRegistry(registry).build();
reporter.start(5, TimeUnit.SECONDS); // 每5秒输出一次

输出示例:

-- Histograms ------------------------------------------------------------------
histogram
             count = 1
               min = 12
               max = 12
              mean = 12.00
            stddev = 0.00
            median = 12.00
              75% <= 12.00
              95% <= 12.00
              98% <= 12.00
              99% <= 12.00
            99.9% <= 12.00

-- Meters ----------------------------------------------------------------------
meter
             count = 200
         mean rate = 40.00 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

4.1 Maven 依赖

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

4.2 使用方式

步骤1:实现健康检查逻辑(继承 HealthCheck):

public class DatabaseHealthCheck extends HealthCheck {
    @Override
    protected Result check() throws Exception {
        // 实际检查逻辑(如数据库连接测试)
        return Result.healthy(); // 或 Result.unhealthy("错误原因")
    }
}

步骤2:注册到 HealthCheckRegistry

HealthCheckRegistry registry = new HealthCheckRegistry();
registry.register("db", new DatabaseHealthCheck());
registry.register("uc", new UserCenterHealthCheck());

// 执行所有检查
Map<String, HealthCheck.Result> results = registry.runHealthChecks();
results.forEach((name, result) -> 
    assertThat(result.isHealthy(), equalTo(true))
);

// 执行单个检查
HealthCheck.Result dbResult = registry.runHealthCheck("db");

5. 模块 metrics-servlets

5.1 Maven 依赖

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

5.2 HealthCheckServlet 健康检查接口

步骤1:暴露 HealthCheckRegistry

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

步骤2:配置 web.xml

<listener>
    <listener-class>com.example.MyHealthCheckContextListener</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>

访问 GET /healthcheck 返回:

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

5.3 ThreadDumpServlet 线程转储

直接配置 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>

访问 GET /threaddump 获取 JVM 线程快照。

5.4 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>

访问 GET /ping 返回 pong(HTTP 200)。

5.5 MetricsServlet 指标接口

步骤1:暴露 MetricRegistry

public class MyMetricsContextListener extends MetricsServlet.ContextListener {
    private static MetricRegistry REGISTRY = new MetricRegistry();
    static {
        REGISTRY.counter("m01-counter").inc();
        REGISTRY.histogram("m02-histogram").update(5);
    }
    @Override
    protected MetricRegistry getMetricRegistry() {
        return REGISTRY;
    }
}

步骤2:配置 web.xml

<listener>
    <listener-class>com.example.MyMetricsContextListener</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>

访问 GET /metrics 返回:

{
  "version": "3.0.0",
  "counters": {
    "m01-counter": { "count": 1 }
  },
  "histograms": {
    "m02-histogram": {
      "count": 1,
      "max": 5,
      "mean": 5.0,
      "min": 5,
      "p50": 5,
      "p75": 5,
      "p95": 5,
      "p98": 5,
      "p99": 5,
      "p999": 5,
      "stddev": 0.0
    }
  }
}

5.6 AdminServlet 管理聚合接口

聚合上述所有 Servlet,配置 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>

访问 /admin 显示包含以下链接的页面:

  • /admin/healthcheck
  • /admin/metrics
  • /admin/ping
  • /admin/threaddump

6. 模块 metrics-servlet

6.1 Maven 依赖

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

6.2 使用方式

步骤1:暴露 MetricRegistry

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

步骤2:配置 web.xml

<listener>
    <listener-class>com.example.MyFilterContextListener</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>

自动收集以下指标:

  • HTTP 状态码计数器(如 200, 404
  • 活跃请求数计数器
  • 请求耗时计时器

7. 其他扩展模块

模块 功能
metrics-jvm JVM 内部指标(内存、GC、线程等)
metrics-ehcache Ehcache 缓存指标
metrics-httpclient Apache HttpClient 4.x 指标
metrics-log4j/metrics-log4j2 Log4j 日志级别速率统计
metrics-logback Logback 日志级别速率统计
metrics-json Jackson 集成(HealthCheckModule/MetricsModule)

此外,第三方库 提供了与其他框架的集成支持。

8. 总结

应用监控是生产环境的刚需,Dropwizard Metrics 提供了简单粗暴的解决方案。本文覆盖了核心模块的使用方式,完整示例代码可在 GitHub 获取。踩坑提示:

  • ❌ 避免在循环中频繁创建指标
  • ✅ 合理使用采样器控制内存开销
  • ⚠️ 生产环境慎用 ConsoleReporter

原始标题:Intro to Dropwizard Metrics